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

后端 未结 16 1785
忘掉有多难
忘掉有多难 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 06:52

    No it's not possible to change the type of an object once instantiated.

    *object = baseObject; doesn't change the type of object, it merely calls a compiler-generated assignment operator.

    It would have been a different matter if you had written

    object = new Base;

    (remembering to call delete naturally; currently your code leaks an object).

    C++11 onwards gives you the ability to move the resources from one object to another; see

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

    0 讨论(0)
  • 2021-02-02 06:54

    There is a simple error in your program. You assign the objects, but not the pointers:

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

    Now you assign baseObject to *object which overwrites the Derived object with a Base object. However, this does work well because you are overwriting an object of type Derived with an object of type Base. The default assignment operator just assigns all members, which in this case does nothing. The object cannot change its type and still is a Derived objects afterwards. In general, this can leads to serious problems e.g. object slicing.

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

    If you instead just assign the pointer it will work as expected, but you just have two objects, one of type Derived and one Base, but I think you want some more dynamic behavior. It sounds like you could implement the specialness as a Decorator.

    You have a base-class with some operation, and several derived classes that change/modify/extend the base-class behavior of that operation. Since it is based on composition it can be changed dynamically. The trick is to store a base-class reference in the Decorator instances and use that for all other functionality.

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

    There are alternative patterns like Strategy which implement different use cases resp. solve different problems. It would probably good to read the pattern documentation with special focus to the Intent and Motivation sections.

    0 讨论(0)
  • 2021-02-02 06:57

    you cannot change to the type of an object after instantiation, as you can see in your example you have a pointer to a Base class (of type base class) so this type is stuck to it until the end.

    • the base pointer can point to upper or down object doesn't mean changed its type:

      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.
      
    • pointers are much strong, flexible, powerful as much dangerous so you should handle them cautiously.

    in your example I can write:

    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
    

    above it's a disaster assigning value to un-allocated pointer. the program will crash.

    in your example you escaped the crash through the memory which was allocated at first:

    object = new Derived;
    

    it's never good idea to assign a value and not address of a subclass object to base class. however in built-in you can but consider this example:

    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;
    

    so the result as not as you guess.

    0 讨论(0)
  • 2021-02-02 06:58

    You can by introducing a variable to the base class, so the memory footprint stays the same. By setting the flag you force calling the derived or the base class implementation.

    #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;
    }
    
    0 讨论(0)
  • 2021-02-02 07:01

    In addition to other answers, you could use function pointers (or any wrapper on them, like std::function) to achieve the necessary 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;
    }
    

    Also, such function pointers approach would allow you to change any of the functions of the objects in run-time, not limiting you to some already defined sets of members implemented in derived classes.

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

    I suggest you use the Strategy Pattern, e.g.

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

    This is a very flexible pattern that has at least a few benefits over trying to deal with the issue through inheritance:

    • You can easily change the behavior of Foo later on by implementing a new Announcer
    • Your Announcers (and your Foos) are easily unit tested
    • You can reuse your Announcers elsewhere int he code

    I suggest you have a look at the age-old "Composition vs. Inheritance" debate (cf. https://www.thoughtworks.com/insights/blog/composition-vs-inheritance-how-choose)

    ps. You've leaked a Derived in your original post! Have a look at std::unique_ptr if it is available.

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