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

后端 未结 16 1786
忘掉有多难
忘掉有多难 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:13
     #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; 
        object->whoami(); 
        Base baseObject;
        object = &baseObject;// this is how you change.
        object->whoami();
        return 0;
    }
    

    output:

    I am Derived                                                                                                                     
    I am Base 
    
    0 讨论(0)
  • 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 <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;
    }
    

    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.

    0 讨论(0)
  • 2021-02-02 07:19

    I would consider regularizing your type.

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

    Now a Base_Value is semantically a value-type that behaves polymorphically.

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

    You could wrap a Base_Value instance in a smart pointer, but I wouldn't bother.

    0 讨论(0)
  • DISCLAIMER: The code here is provided as means to understand an idea, not to be implemented in production.

    You're using inheritance. It can achieve 3 things:

    • Add fields
    • Add methods
    • replace virtual methods

    Out of all those features, you're using only the last one. This means that you're not actually forced to rely on inheritance. You can get the same results by many other means. The simplest is to keep tabs on the "type" by yourself - this will allow you to change it on the fly:

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

    Without inheritance the "type" is yours to do whatever you want. You can changeType at any time it suits you. With that power also comes responsibility: the compiler will no longer make sure the type is correct or even set at all. You have to ensure it or you'll get hard to debug runtime errors.

    You may wrap it in inheritance just as well, eg. to get a drop-in replacement for existing code:

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

    OR (slightly uglier):

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

    This solution is easy to implement and easy to understand. But with more options in the switch and more code in each path it gets very messy. So the very first step is to refactor the actual code out of the switch and into self-contained functions. Where better to keep than other than Derivied class?

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

    Then you can replace the switch with an external class that implements it via virtual inheritance and TADA! We've reinvented the Strategy Pattern, as others have said in the first place : )

    The bottom line is: whatever you do, you're not inheriting the main class.

    0 讨论(0)
提交回复
热议问题