Подтвердить что ты не робот

Можно ли изменить класс объектов С++ после создания экземпляра?

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

Здесь ситуация: я хочу, чтобы специальность этих субклассифицированных объектов была расходуемой. По сути, я хотел бы реализовать функцию 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;
}
4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

Было бы иначе, если бы вы написали

object = new Base;

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

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

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

Ответ 4

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

Стандарт С++ явно обращается к этой идее в разделе 3.8 (Object Lifetime):

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

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

, если

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

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

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

Однако, как знаменитая цитата: "Любая проблема в информатике может быть решена путем добавления слоя косвенности" , и это верно здесь тоже.

Вместо предлагаемого вами метода

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

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

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

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

Ответ 5

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

#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. Вы должны серьезно подумать о разработке своего приложения, чтобы изменить указатель при изменении типа объекта.

Ответ 6

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

#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 позже, внедрив новый анонс
  • Ваши анонтеры (и ваши фоны) легко проверяются на модуле.
  • Вы можете повторно использовать своих анонтеров в другом месте внутри кода

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

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

Ответ 7

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

#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;
}

Ответ 8

В дополнение к другим ответам вы можете использовать указатели функций (или любую обертку на них, например 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;
}

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

Ответ 9

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

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;
}

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

Ответ 10

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

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;
  }
};

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

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

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

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

Ответ 11

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

#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! Это важно для полиморфных классов, поэтому вы вызываете правильный деструктор для этого экземпляра, а не деструктор базового класса.) Это может сэкономить вам дополнительные байты на экземплярах меньших классов. Однако вам нужно будет поиграть с умными указателями, чтобы получить безопасное автоматическое удаление. Одно из преимуществ объединений над интеллектуальными указателями на динамическую память состоит в том, что вам не нужно выделять или освобождать больше объектов в куче, но вы можете просто повторно использовать имеющуюся у вас память.

Ответ 12

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

Вы используете наследование. Он может достичь 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! Мы изобрели шаблон стратегии, как говорили другие, в первую очередь:)

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

Ответ 13

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

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

    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;

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

Ответ 14

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

Оба требуют, чтобы вы обеспечивали предоставление downcasts от 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; }
};

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

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>();

который изменяет удерживаемый объект, сохраняющий элементы данных (фактически использует cast 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, оператор разыменования и т.д.

Ответ 15

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

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

Обратите внимание, что, как и ваше попытанное назначение, это заменит переменные-члены в *object на те, что были построены baseObject, вероятно, не то, что вы действительно хотите, поэтому вам придется скопировать исходные переменные-члены в new baseObject сначала, используя оператор присваивания или конструктор копирования до memcpy, то есть

Base baseObject = *object;

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

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

object = new Base(*object);

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

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