2016-12-19 1 views
41

У меня есть куча классов, которые все наследуют одни и те же атрибуты из общего базового класса. Базовый класс реализует некоторые виртуальные функции, которые работают в общих случаях, в то время как каждый подкласс повторно реализует эти виртуальные функции для множества особых случаев.Можно ли изменить класс объекта C++ после создания экземпляра?

В следующей ситуации: Я хочу, чтобы специальность этих предметов была разделена на одно целое. По сути, я хотел бы реализовать функцию expend(), которая заставляет объект потерять свой идентификатор подкласса и вернуться к тому, чтобы быть экземпляром базового класса с обычным поведением, реализованным в базовом классе.

Следует отметить, что производные классы не вводят никаких дополнительных переменных, поэтому базовый и производный классы должны иметь одинаковый размер в памяти.

Я открыт для уничтожения старого объекта и создания нового, если я могу создать новый объект по одному и тому же адресу памяти, поэтому существующие указатели не сломаны.

Следующая попытка не работает и производит некоторые, казалось бы, неожиданные действия. Что мне здесь не хватает?

#include <iostream> 

class Base { 
public: 
    virtual void whoami() { 
     std::cout << "I am Base\n"; 
    } 
}; 

class Derived : public Base { 
public: 
    void whoami() { 
     std::cout << "I am Derived\n"; 
    } 
}; 

Base* object; 

int main() { 
    object = new Derived; //assign a new Derived class instance 
    object->whoami(); //this prints "I am Derived" 

    Base baseObject; 
    *object = baseObject; //reassign existing object to a different type 
    object->whoami(); //but it *STILL* prints "I am Derived" (!) 

    return 0; 
} 
+3

Помните, что C++ не является динамическим языком, как только он скомпилирован, все исправлено. –

+10

Что вы, кажется, просите, это приложение размещения нового, но я не буду давать ответа - это плохой дизайн IMO. – matb

+7

Возможно, вы можете использовать стратегию разработки pantern вместо класса hierachy –

ответ

35

Вы можете заплатить за нарушение передового опыта и сохранение небезопасного кода. Другие ответы предоставят вам неприятные трюки для достижения этого.

Мне не нравятся ответы, которые просто говорят: «Вы не должны этого делать», но я хотел бы предложить, вероятно, лучший способ добиться результата, к которому вы стремитесь.

strategy pattern, как было предложено в комментарии от @ manni66, является хорошим.

Вы также должны думать о data oriented design, так как иерархия классов не выглядит разумным выбором в вашем случае.

+0

Когда он делает * object = baseObject, кто-нибудь знает, скопирует ли vtable в baseObject на производный объект? Если это так, то когда он вызывает object-> whoamI(), не будет вызывать базовую версию функции? – Zebrafish

+0

Ничего, очевидно, нет. В моей реализации vtable кажется 8 байтами, и он не копируется. Интересно, однако, если я memcpy sizeof (Base) вместо его назначения, он вызывает базовую версию. Это похоже на отличную возможность. – Zebrafish

+7

@TitoneMaurice: Я слышал, что Undefined Behavior описано с использованием многих инсайтов, но это первый раз, когда я слышал, что это называется «круто». –

16

Да и нет. Класс C++ определяет тип области памяти, являющейся объектом. Как только область памяти была создана, устанавливается ее тип. Вы можете попробовать работать около тип системы уверен, но компилятор не позволит вам уйти от него. Рано или поздно он выстрелит вам в ногу, потому что компилятор сделал предположение о тех типах, которые вы нарушили, и нет возможности остановить компилятор от такого допущения переносимым образом.

Однако есть шаблон дизайна для этого: это «Состояние». Вы извлекаете изменения в свою собственную иерархию классов с собственным базовым классом, и у вас есть свои объекты, которые хранят указатель на абстрактную государственную базу этой новой иерархии. Затем вы можете обменять их на свои сердца.

+1

Я думаю, что шаблон, который вы описываете, более часто упоминается как [шаблон стратегии] (https://en.wikipedia.org/wiki/Strategy_pattern) –

+0

@QPaysTaxes - Это то, что назвал его GoF. Но они тесно связаны. Во всяком случае, я полагал, что OP хотел изменить макет объектов, поэтому я назвал * этот * шаблон. Я уйду, как и для разнообразия. – StoryTeller

+0

Справедливо. Я просто хотел, чтобы у людей было гораздо более длинное, более подробное объяснение: –

8

Вы можете ввести переменную в базовый класс, чтобы след памяти оставался прежним. Установив флаг, вы принудительно вызываете производную или базовую реализацию класса.

#include <iostream> 

class Base { 
public: 
    Base() : m_useDerived(true) 
    { 
    } 

    void setUseDerived(bool value) 
    { 
     m_useDerived = value; 
    } 

    void whoami() { 
     m_useDerived ? whoamiImpl() : Base::whoamiImpl(); 
    } 

protected: 
    virtual void whoamiImpl() { std::cout << "I am Base\n"; } 

private: 
    bool m_useDerived; 
}; 

class Derived : public Base { 
protected: 
    void whoamiImpl() { 
     std::cout << "I am Derived\n"; 
    } 
}; 

Base* object; 

int main() { 
    object = new Derived; //assign a new Derived class instance 
    object->whoami(); //this prints "I am Derived" 

    object->setUseDerived(false); 
    object->whoami(); //should print "I am Base" 

    return 0; 
} 
14

Невозможно изменить тип объекта после его создания.

*object = baseObject; не меняет Типу из object, он просто вызывает оператор присваивания сгенерированный компилятором.

Было бы другое дело, если бы вы написали

object = new Base;

(напомним, называть delete естественно, в настоящее время код утечки объекта).

C++ 11 onwards дает вам возможность перемещать ресурсы с одного объекта на другой; см

http://en.cppreference.com/w/cpp/utility/move

+0

Но это изменило бы адрес объекта. Существующие указатели на прежний объект затем указывают на неопределенную память. – dwk

+2

Абсолютно. Но ничто не остановит вас от создания класса, который управляет этим поведением. Вы даже можете перегрузить адрес оператора. – Bathsheba

+0

На самом деле, в большинстве компиляторов вы можете переписать указатель vtable, который даст точно то, что хочет OP. Хороший трюк, чтобы произвести впечатление на ваших друзей, ужасный взлом в реальном коде. –

7

В дополнение к другим ответам, вы можете использовать указатели на функции (или любой обертку на них, как std::function) для достижения необходимого bevahior:

void print_base(void) { 
    cout << "This is base" << endl; 
} 

void print_derived(void) { 
    cout << "This is derived" << endl; 
} 

class Base { 
public: 
    void (*print)(void); 

    Base() { 
     print = print_base; 
    } 
}; 

class Derived : public Base { 
public: 
    Derived() { 
     print = print_derived; 
    } 
}; 

int main() { 
    Base* b = new Derived(); 
    b->print(); // prints "This is derived" 
    *b = Base(); 
    b->print(); // prints "This is base" 
    return 0; 
} 

Кроме того, такие указатели на функции подход позволит вам изменить любую из функций объектов во время выполнения, а не ограничивать вас некоторыми уже определенными наборами элементов, реализованных в производных классах.

+0

Также называется стратегическим шаблоном – WorldSEnder

+1

Я бы назвал его «reimplementing vtables in code» - просто добавьте struct/class для хранения нескольких указателей на функции, и у вас есть именно то, что C++ делает под капотом (с той лишь разницей, что вы теперь свободны для управления vtable (-pointer) по желанию). – cmaster

9

Вы можете делать то, что вы буквально просите, с размещением нового и явным вызовом деструктора. Что-то вроде этого:

#include <iostream> 
#include <stdlib.h> 

class Base { 
public: 
    virtual void whoami() { 
     std::cout << "I am Base\n"; 
    } 
}; 

class Derived : public Base { 
public: 
    void whoami() { 
     std::cout << "I am Derived\n"; 
    } 
}; 

union Both { 
    Base base; 
    Derived derived; 
}; 

Base *object; 

int 
main() { 
    Both *tmp = (Both *) malloc(sizeof(Both)); 
    object = new(&tmp->base) Base; 

    object->whoami(); 

    Base baseObject; 
    tmp = (Both *) object; 
    tmp->base.Base::~Base(); 
    new(&tmp->derived) Derived; 

    object->whoami(); 

    return 0; 
} 

Однако, поскольку matb сказал, это действительно не очень хороший дизайн. Я бы рекомендовал пересмотреть то, что вы пытаетесь сделать. Некоторые из других ответов здесь также могут решить вашу проблему, но я думаю, что что-либо по идее того, что вы просите, будет kludge. Вам следует серьезно подумать о разработке приложения, чтобы вы могли изменить указатель при изменении типа объекта.

+4

Я считал (но не голосовал), потому что первое предложение ошибочно. Вы можете ** не ** * изменять * класс объекта. Можно заменить объект другим другим классом, как и в вашем коде. –

+3

Я точно так же склоняюсь к нулю. Сообщите мне, когда это исправлено. –

+1

Это вызывает неопределенное поведение? – Kos

3

Простая ошибка в вашей программе. Вы назначаете объекты, но не указатели:

int main() { 
    Base* object = new Derived; //assign a new Derived class instance 
    object->whoami(); //this prints "I am Derived" 

    Base baseObject; 

Теперь вы присваивают baseObject к *object, который переписывает Derived объект с Base объекта. Однако это хорошо работает, потому что вы переписываете объект типа Derived с объектом типа Base. Оператор присваивания по умолчанию присваивает всем членам, что в этом случае ничего не делает. Объект не может изменить свой тип и по-прежнему представляет собой объект Derived. В общем, это может привести к серьезным проблемам, например. срез объектов.

*object = baseObject; //reassign existing object to a different type 
    object->whoami(); //but it *STILL* prints "I am Derived" (!) 

    return 0; 
} 

Если вы вместо того, чтобы просто присвоить указатель будет работать, как ожидалось, но вы просто иметь два объекта, один из типов Derived и один Base, но я думаю, что вы хотите, чтобы некоторые более динамическое поведение. Похоже, вы могли бы реализовать специальность как Decorator.

У вас есть базовый класс с некоторой операцией и несколько производных классов, которые изменяют/изменяют/расширяют поведение базового класса этой операции. Поскольку он основан на композиции, он может быть изменен динамически. Хитрость заключается в том, чтобы хранить ссылку базового класса в экземплярах Decorator и использовать ее для всех других функций.

class Base { 
public: 
    virtual void whoami() { 
     std::cout << "I am Base\n"; 
    } 

    virtual void otherFunctionality() {} 
}; 

class Derived1 : public Base { 
public: 
    Derived1(Base* base): m_base(base) {} 

    virtual void whoami() override { 
     std::cout << "I am Derived\n"; 

     // maybe even call the base-class implementation 
     // if you just want to add something 
    } 

    virtual void otherFunctionality() { 
     base->otherFunctionality(); 
    } 
private: 
    Base* m_base; 
}; 

Base* object; 

int main() { 
    Base baseObject; 
    object = new Derived(&baseObject); //assign a new Derived class instance 
    object->whoami(); //this prints "I am Derived" 

    // undecorate 
    delete object; 
    object = &baseObject; 

    object->whoami(); 

    return 0; 
} 

Существуют альтернативные шаблоны, такие как стратегия, которые реализуют различные варианты использования соответственно. решать разные проблемы. Было бы неплохо прочитать документацию по шаблонам с особым вниманием к разделам «Намерение и мотивация».

+0

Декоратор не работает. Когда вы дебеторируете, вы нарушаете другие указатели. Это не то, чего хочет OP. – atoMerz

+0

@atoMerz Было бы хорошо, если бы OP поставил еще какой-то контекст, чего он пытается достичь. Сейчас это скорее решение неизвестной проблемы, которая не работает. Декораторы работают очень хорошо, если вы хотите динамически изменять поведение объекта, поэтому я представил этот вариант, так как другие представили стратегию. – Jens

+0

У ИМО вопрос достаточно подробный, что не имеет значения. Потому что даже учитывая текущую информацию о проблеме, шаблон стратегии работает, а декоратор терпит неудачу. В частности, ваше решение делает недействительными другие указатели и вообще не изменяет их поведение. Это то, о чем попросил ОП. Это изменение должно повлиять на все указатели и не сломать их. – atoMerz

3

Я бы рассмотрел вопрос о регуляризации вашего типа.

class Base { 
public: 
    virtual void whoami() { std::cout << "Base\n"; } 
    std::unique_ptr<Base> clone() const { 
    return std::make_unique<Base>(*this); 
    } 
    virtual ~Base() {} 
}; 
class Derived: public Base { 
    virtual void whoami() overload { 
    std::cout << "Derived\n"; 
    }; 
    std::unique_ptr<Base> clone() const override { 
    return std::make_unique<Derived>(*this); 
    } 
public: 
    ~Derived() {} 
}; 
struct Base_Value { 
private: 
    std::unique_ptr<Base> pImpl; 
public: 
    void whoami() { 
    pImpl->whoami(); 
    } 
    template<class T, class...Args> 
    void emplace(Args&&...args) { 
    pImpl = std::make_unique<T>(std::forward<Args>(args)...); 
    } 
    Base_Value()=default; 
    Base_Value(Base_Value&&)=default; 
    Base_Value& operator=(Base_Value&&)=default; 
    Base_Value(Base_Value const&o) { 
    if (o.pImpl) pImpl = o.pImpl->clone(); 
    } 
    Base_Value& operator=(Base_Value&& o) { 
    auto tmp = std::move(o); 
    swap(pImpl, tmp.pImpl); 
    return *this; 
    } 
}; 

Теперь Base_Value семантически значение типа, который ведет себя полиморфно.

Base_Value object; 
object.emplace<Derived>(); 
object.whoami(); 

object.emplace<Base>(); 
object.whoami(); 

Вы можете обернуть Base_Value экземпляр в смарт-указатель, но я бы не стал.

11

Я открыт для уничтожения старого объекта и создания нового, если я могу создать новый объект по одному и тому же адресу памяти, поэтому существующие указатели не сломаны.

Стандарт C++ явно решает эту идею в разделе 3.8 (Lifetime Object):

Если, после того, как время жизни объекта закончилась и перед хранением которой объект занимал повторно используется или выпущен, новый объект создается в месте хранения, в котором был загружен исходный объект, указатель, указывающий на исходный объект, ссылку, относящуюся к исходному объекту, или имя исходного объекта будут автоматически ссылаться на новый объект и, как только время жизни нового объекта запустится, может быть использован для манипулирования нового объекта < надреза >

Ого, это именно то, что вы хотели. Но я не показал всего правила. Вот остальное:

если:

  • хранения для нового объекта точно перекрывает место хранения, которое исходный объект занимаемую и
  • новый объект того же типа как исходный объект (игнорирующий cv-квалификаторы верхнего уровня) и
  • Тип исходного объекта не является константным и если тип класса не содержит нестатических да ta член, тип которого является const-квалифицированным или ссылочным, и
  • Первоначальный объект был самым производным объектом (1.8) типа T, а новый объект является наиболее производным объектом типа T (то есть, они не являются субобъекты базового класса).

Так что ваша идея была мысль комитетом языка и, в частности незаконным, в том числе подлый обходной путь, что «у меня есть базовый класс субобъектом правильного типа, я просто сделать новый объект его место ", которое последняя точка выстрела останавливается на своем пути.

Вы можете заменить объект объектом другого типа, как показывает ответ от RossRidge. Или вы можете заменить объект и продолжать использовать указатели, существовавшие до замены. Но вы не можете обойтись вместе.

Однако, как и the famous quote: "Any problem in computer science can be solved by adding a layer of indirection", и это верно и здесь.

Вместо вашего предложенного метода

Derived d; 
Base* p = &d; 
new (p) Base(); // makes p invalid! Plus problems when d's destructor is automatically called 

Вы можете сделать:

unique_ptr<Base> p = make_unique<Derived>(); 
p.reset(make_unique<Base>()); 

Если скрыть этот указатель и легкое-на руку внутри другого класса, вы будете иметь «шаблон проектирования «такие как государство или стратегия, упомянутые в других ответах. Но все они полагаются на один дополнительный уровень косвенности.

9

Предлагаю вам использовать шаблон стратегии, например.

#include <iostream> 

class IAnnouncer { 
public: 
    virtual ~IAnnouncer() { } 
    virtual void whoami() = 0; 
}; 

class AnnouncerA : public IAnnouncer { 
public: 
    void whoami() override { 
     std::cout << "I am A\n"; 
    } 
}; 

class AnnouncerB : public IAnnouncer { 
public: 
    void whoami() override { 
     std::cout << "I am B\n"; 
    } 
}; 

class Foo 
{ 
public: 
    Foo(IAnnouncer *announcer) : announcer(announcer) 
    { 
    } 
    void run() 
    { 
     // Do stuff 
     if(nullptr != announcer) 
     { 
      announcer->whoami(); 
     } 
     // Do other stuff 
    } 
    void expend(IAnnouncer* announcer) 
    { 
     this->announcer = announcer; 
    } 
private: 
    IAnnouncer *announcer; 
}; 


int main() { 
    AnnouncerA a; 
    Foo foo(&a); 

    foo.run(); 

    // Ready to "expend" 
    AnnouncerB b; 
    foo.expend(&b); 

    foo.run(); 

    return 0; 
} 

Это очень гибкая модель, которая имеет по крайней мере несколько преимуществ по сравнению с пытаются справиться с проблемой путем наследования:

  • Вы можете легко изменить поведение Foo позже путем внедрения новой дикторы
  • Ваших Оповещателей (и ваш Foos) легко протестирован
  • Вы можете повторно использовать диктор в другом месте ИНТА он код

Предлагаю вам взглянуть на вековую дискуссию «Композиция против наследования» (ср. https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose)

пс. Вы просочились в свое оригинальное сообщение! Посмотрите на std :: unique_ptr, если он доступен.

1

Я не согласен с советом, что это не отличный дизайн, но другой безопасный способ сделать это - это объединение, которое может удерживать любой из классов, которые вы хотите переключить, поскольку стандарт гарантирует это могут безопасно удерживать любой из них. Вот версия, которая инкапсулирует все детали внутри самого союза:

#include <cassert> 
#include <cstdlib> 
#include <iostream> 
#include <new> 
#include <typeinfo> 

class Base { 
public: 
    virtual void whoami() { 
     std::cout << "I am Base\n"; 
    } 

    virtual ~Base() {} // Every base class with child classes that might be deleted through a pointer to the 
         // base must have a virtual destructor! 
}; 

class Derived : public Base { 
public: 
    void whoami() { 
     std::cout << "I am Derived\n"; 
    } 
    // At most one member of any union may have a default member initializer in C++11, so: 
    Derived(bool) : Base() {} 
}; 

union BorD { 
    Base b; 
    Derived d; // Initialize one member. 

    BorD(void) : b() {} // These defaults are not used here. 
    BorD(const BorD&) : b() {} // No per-instance data to worry about! 
           // Otherwise, this could get complicated. 
    BorD& operator= (const BorD& x) // Boilerplate: 
    { 
     if (this != &x) { 
      this->~BorD(); 
      new(this) BorD(x); 
     } 
     return *this; 
    } 

    BorD(const Derived& x) : d(x) {} // The constructor we use. 
    // To destroy, be sure to call the base class’ virtual destructor, 
    // which works so long as every member derives from Base. 
    ~BorD(void) { dynamic_cast<Base*>(&this->b)->~Base(); } 

    Base& toBase(void) 
    { // Sets the active member to b. 
     Base* const p = dynamic_cast<Base*>(&b); 

     assert(p); // The dynamic_cast cannot currently fail, but check anyway. 
     if (typeid(*p) != typeid(Base)) { 
      p->~Base();  // Call the virtual destructor. 
      new(&b) Base; // Call the constructor. 
     } 
     return b; 
    } 
}; 

int main(void) 
{ 
    BorD u(Derived{false}); 

    Base& reference = u.d; // By the standard, u, u.b and u.d have the same address. 

    reference.whoami(); // Should say derived. 
    u.toBase(); 
    reference.whoami(); // Should say base. 

    return EXIT_SUCCESS; 
} 

Более простой способ, чтобы получить то, что вы хотите, вероятно, для того чтобы держать контейнер Base * и заменить элементы в отдельности по мере необходимости с new и delete. (Не забывайте объявлять о своем деструкторе virtual! Это важно для полиморфных классов, поэтому вы вызываете правильный деструктор для этого экземпляра, а не деструктор базового класса. Это может сэкономить вам дополнительные байты для экземпляров меньших классов. Однако вам нужно будет поиграть с умными указателями, чтобы получить безопасное автоматическое удаление. Одно из преимуществ объединений над интеллектуальными указателями на динамическую память состоит в том, что вам не нужно выделять или освобождать больше объектов в куче, но вы можете просто повторно использовать имеющуюся у вас память.

0

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Кодекс здесь представлен как средство для понимания идеи, не подлежащей внедрению в производство.

Вы используете наследование.Он может достичь 3 вещи:

  • Добавление полей
  • Добавить методы
  • заменить виртуальные методы

Из всех этих функций, вы используете только последний. Это означает, что вы на самом деле не вынуждены полагаться на наследование. Вы можете получить те же результаты другими способами. Проще всего следить за «тип» по себе - это позволит вам изменить его на лету:

#include <stdexcept> 

enum MyType { BASE, DERIVED }; 

class Any { 
private: 
    enum MyType type; 
public: 
    void whoami() { 
     switch(type){ 
      case BASE: 
       std::cout << "I am Base\n"; 
       return; 
      case DERIVED: 
       std::cout << "I am Derived\n"; 
       return; 
     } 
     throw std::runtime_error("undefined type"); 
    } 
    void changeType(MyType newType){ 
     //insert some checks if that kind of transition is legal 
     type = newType; 
    } 
    Any(MyType initialType){ 
     type = initialType; 
    } 

}; 

без наследования «типа» твой, чтобы делать все, что вы хотите. Вы можете changeType в любое удобное для вас время. С этой силой также приходит ответственность: компилятор больше не будет уверен, что тип правильный или даже установлен вообще. Вы должны убедиться в этом, или вам будет трудно отлаживать ошибки времени выполнения.

Вы также можете обернуть его в наследование, например. чтобы получить падение в замену существующего кода:

class Base : Any { 
public: 
    Base() : Any(BASE) {} 
}; 

class Derived : public Any { 
public: 
    Derived() : Any(DERIVED) {} 
}; 

ИЛИ (слегка нелицеприятную):

class Derived : public Base { 
public: 
    Derived : Base() { 
     changeType(DERIVED) 
    } 
}; 

Это решение легко реализовать и легко понять. Но с большим количеством опций в коммутаторе и большим количеством кода в каждом пути он становится очень грязным. Итак, самый первый шаг - реорганизовать фактический код из коммутатора и в автономные функции. Где лучше держаться, кроме класса Derivied?

class Base { 
public: 
    static whoami(Any* This){ 
     std::cout << "I am Base\n"; 
    } 
}; 

class Derived { 
public: 
    static whoami(Any* This){ 
     std::cout << "I am Derived\n"; 
    } 
}; 

/*you know where it goes*/ 
    switch(type){ 
     case BASE: 
      Base:whoami(this); 
      return; 
     case DERIVED: 
      Derived:whoami(this); 
      return; 
    } 

Затем вы можете заменить коммутатор внешним классом, который реализует его через виртуальное наследование и TADA! Мы изобрели шаблон стратегии, как говорили другие, в первую очередь:)

Суть в том, что бы вы ни делали, вы не наследуете основной класс.

-2

Назначение присваивает переменные-члены, а не указатель, используемый для вызовов функций виртуальных членов. Вы можете легко заменить, что с полной копией памяти:

//*object = baseObject; //this assignment was wrong 
memcpy(object, &baseObject, sizeof(baseObject)); 

Обратите внимание, что так же, как ваш предпринятая переуступка, это было бы заменить переменные члены в *object с теми из вновь построенных baseObject - вероятно, не то, что вы на самом деле хотите, так что вы» придется скопировать исходные переменные члены нового BaseObject первых, с помощью оператора либо присваивания или конструктор копирования перед memcpy, т.е.

Base baseObject = *object; 

можно скопировать только указатель виртуальной функции таблицы, но это будет зависеть от внутреннего знания о том, как t он компилятор хранит его, поэтому не рекомендуется.

Если сохранение объекта по одному и тому же адресу памяти не имеет решающего значения, более простой и лучший подход будет противоположным: постройте новый базовый объект и скопируйте переменные-члены исходного объекта поверх - например, используйте конструктор копирования.

object = new Base(*object); 

Но вы также должны удалить исходный объект, так что выше один лайнер будет недостаточно - вы должны помнить оригинальный указатель на другую переменную, чтобы удалить его и т.д. Если у вас есть несколько ссылок на этот оригинальный объект, который вам нужно будет обновить, а иногда это может быть довольно сложно. Тогда путь memcpy лучше.

Если некоторые из переменных-членов сами являются указателями на объекты, которые создаются/удаляются в конструкторе/деструкторе главного объекта, или если у них есть более специализированный оператор присваивания или другая пользовательская логика, у вас будет еще больше работы над ваши руки, но для тривиальных переменных-членов это должно быть достаточно хорошим.

+1

Этот ответ - рецепт катастрофы. 'Memcpy'ing объекта работает только в том случае, если он тривиально скопирован. В случае OPs класс является полиморфным и, следовательно, не является тривиально-копируемым. Memcpying объекта UB и, следовательно, серьезная ошибка в вашей программе. – Jens

+0

Объект с виртуальными методами по определению не является «тривиально скопируемым», но если это единственный фактор, делающий его таким, т. Е. Все его членные переменные тривиально можно копировать, то memcpy идеально подходит для этого конкретного вопроса. – user7321148

+0

Также вы можете учитывать любую другую проблему и работать вокруг нее, это не UB, вам просто нужно понять, что вы делаете. – user7321148

0

Вы не можете изменить тип объекта после создания экземпляра, как вы можете видеть в своем примере, у вас есть указатель на базовый класс (базового класса типа), чтобы этот тип застрял до конца.

  • базовый указатель может указывать на верхний или вниз объект не означает перепрофилирован:

    Base* ptrBase; // pointer to base class (type) 
    ptrBase = new Derived; // pointer of type base class `points to an object of derived class` 
    
    Base theBase; 
    ptrBase = &theBase; // not *ptrBase = theDerived: Base of type Base class points to base Object. 
    
  • указатели являются очень сильными, гибкими, мощными, насколько опасно, так что вы должны обращаться с ними осторожно ,

в вашем примере я могу написать:

Base* object; // pointer to base class just declared to point to garbage 
Base bObject; // object of class Base 
*object = bObject; // as you did in your code 

выше это катастрофа назначая значение нераспределенного указателя. программа выйдет из строя.

в вашем примере вы избежали аварии через память, которая была выделена на первый:

object = new Derived; 

это никогда не хорошая идея, чтобы назначить value and not address объекта подкласса для базового класса. однако во встроенном примере вы можете рассмотреть этот пример:

int* pInt = NULL; 

int* ptrC = new int[1]; 
ptrC[0] = 1; 

pInt = ptrC; 

for(int i = 0; i < 1; i++) 
    cout << pInt[i] << ", "; 
cout << endl; 

int* ptrD = new int[3]; 
ptrD[0] = 5; 
ptrD[1] = 7; 
ptrD[2] = 77; 

*pInt = *ptrD; // copying values of ptrD to a pointer which point to an array of only one element! 
// the correct way: 
// pInt = ptrD; 

for(int i = 0; i < 3; i++) 
    cout << pInt[i] << ", "; 
cout << endl; 

так что результат не так, как вы предполагаете.

0

У меня есть 2 решения. Более простой, который не сохраняет адрес памяти, и тот, который сохраняет адрес памяти.

Оба требуют, чтобы вы предоставляли подавление от Base to Derived, что не является проблемой в вашем случае.

struct Base { 
    int a; 
    Base(int a) : a{a} {}; 
    virtual ~Base() = default; 
    virtual auto foo() -> void { cout << "Base " << a << endl; } 
}; 
struct D1 : Base { 
    using Base::Base; 
    D1(Base b) : Base{b.a} {}; 
    auto foo() -> void override { cout << "D1 " << a << endl; } 
}; 
struct D2 : Base { 
    using Base::Base; 
    D2(Base b) : Base{b.a} {}; 
    auto foo() -> void override { cout << "D2 " << a << endl; } 
}; 

Для прежнего вы можете создать смарт-указатель, который может по-видимому изменять сохраненные данные между произведенными (и базовых) классов:

template <class B> struct Morpher { 
    std::unique_ptr<B> obj; 

    template <class D> auto morph() { 
    obj = std::make_unique<D>(*obj); 
    } 

    auto operator->() -> B* { return obj.get(); } 
}; 

int main() { 
    Morpher<Base> m{std::make_unique<D1>(24)}; 
    m->foo();  // D1 24 

    m.morph<D2>(); 
    m->foo();  // D2 24 
} 

Магия в

m.morph<D2>(); 

, который изменяет удерживаемый объект, сохраняя элементы данных (фактически использует литье ctor).


Если вам нужно сохранить ячейку памяти, вы можете адаптировать выше использовать буфер и размещение новых вместо unique_ptr. Это немного больше работы в целом гораздо больше внимания, чтобы заплатить, но это дает вам именно то, что вам нужно:

template <class B> struct Morpher { 
    std::aligned_storage_t<sizeof(B)> buffer_; 
    B *obj_; 

    template <class D> 
    Morpher(const D &new_obj) 
     : obj_{new (&buffer_) D{new_obj}} { 
    static_assert(std::is_base_of<B, D>::value && sizeof(D) == sizeof(B) && 
        alignof(D) == alignof(B)); 
    } 
    Morpher(const Morpher &) = delete; 
    auto operator=(const Morpher &) = delete; 
    ~Morpher() { obj_->~B(); } 

    template <class D> auto morph() { 
    static_assert(std::is_base_of<B, D>::value && sizeof(D) == sizeof(B) && 
        alignof(D) == alignof(B)); 

    obj_->~B(); 
    obj_ = new (&buffer_) D{*obj_}; 
    } 

    auto operator->() -> B * { return obj_; } 
}; 

int main() { 
    Morpher<Base> m{D1{24}}; 
    m->foo(); // D1 24 

    m.morph<D2>(); 
    m->foo(); // D2 24 

    m.morph<Base>(); 
    m->foo(); // Base 24 
} 

Это, конечно, абсолютная голой кости. Вы можете добавить move ctor, оператор разыменования и т. Д.