Is it possible to change a C++ object's class after instantiation?

后端 未结 16 1801
忘掉有多难
忘掉有多难 2021-02-02 06:50

I have a bunch of classes which all inherit the same attributes from a common base class. The base class implements some virtual functions that work in general cases, whilst eac

16条回答
  •  小蘑菇
    小蘑菇 (楼主)
    2021-02-02 07:14

    I don’t disagree with the advice that this isn’t a great design, but another safe way to do it is with a union that can hold any of the classes you want to switch between, since the standard guarantees it can safely hold any of them. Here’s a version that encapsulates all the details inside the union itself:

    #include 
    #include 
    #include 
    #include 
    #include 
    
    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(&this->b)->~Base(); }
    
        Base& toBase(void)
        {  // Sets the active member to b.
           Base* const p = dynamic_cast(&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;
    }
    

    A simpler way to get what you want is probably to keep a container of Base * and replace the items individually as needed with new and delete. (Still remember to declare your destructor virtual! That’s important with polymorphic classes, so you call the right destructor for that instance, not the base class’ destructor.) This might save you some extra bytes on instances of the smaller classes. You would need to play around with smart pointers to get safe automatic deletion, though. One advantage of unions over smart pointers to dynamic memory is that you don’t have to allocate or free any more objects on the heap, but can just re-use the memory you have.

提交回复
热议问题