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 each subclass re-implements those virtual functions for a variety of special cases.
Here's the situation: I want the special-ness of these sub-classed objects to be expendable. Essentially, I would like to implement an expend()
function which causes an object to lose its sub-class identity and revert to being a base-class instance with the general-case behaviours implemented in the base class.
I should note that the derived classes don't introduce any additional variables, so both the base and derived classes should be the same size in memory.
I'm open to destroying the old object and creating a new one, as long as I can create the new object at the same memory address, so existing pointers aren't broken.
The following attempt doesn't work, and produces some seemingly unexpected behaviour. What am I missing here?
#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;
}
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:
A simpler way to get what you want is probably to keep a container of
Base *
and replace the items individually as needed withnew
anddelete
. (Still remember to declare your destructorvirtual
! 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.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:
pointers are much strong, flexible, powerful as much dangerous so you should handle them cautiously.
in your example I can write:
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:
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:so the result as not as you guess.
The C++ Standard explicitly addresses this idea in section 3.8 (Object Lifetime):
Oh wow, this is exactly what you wanted. But I didn't show the whole rule. Here's the rest:
So your idea has been thought of by the language committee and specifically made illegal, including the sneaky workaround that "I have a base class subobject of the right type, I'll just make a new object in its place" which the last bullet point stops in its tracks.
You can replace an object with an object of a different type as @RossRidge's answer shows. Or you can replace an object and keep using pointers that existed before the replacement. But you cannot do both together.
However, like the famous quote: "Any problem in computer science can be solved by adding a layer of indirection" and that is true here too.
Instead of your suggested method
You can do:
If you hide this pointer and slight-of-hand inside another class, you'll have the "design pattern" such as State or Strategy mentioned in other answers. But they all rely on one extra level of indirection.
I suggest you use the Strategy Pattern, e.g.
This is a very flexible pattern that has at least a few benefits over trying to deal with the issue through inheritance:
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.
Your assignment only assigns member variables, not the pointer used for virtual member function calls. You can easily replace that with full memory copy:
Note that much like your attempted assignment, this would replace member variables in
*object
with those of the newly constructedbaseObject
- probably not what you actually want, so you'll have to copy the original member variables to the new baseObject first, using either assignment operator or copy constructor before thememcpy
, i.e.It is possible to copy just the virtual functions table pointer but that would rely on internal knowledge about how the compiler stores it so is not recommended.
If keeping the object at the same memory address is not crucial, a simpler and so better approach would be the opposite - construct a new base object and copy the original object's member variables over - i.e. use a copy constructor.
But you'll also have to delete the original object, so the above one-liner won't be enough - you need to remember the original pointer in another variable in order to delete it, etc. If you have multiple references to that original object you'll need to update them all, and sometimes this can be quite complicated. Then the
memcpy
way is better.If some of the member variables themselves are pointers to objects that are created/deleted in the main object's constructor/destructor, or if they have a more specialized assignment operator or other custom logic, you'll have some more work on your hands, but for trivial member variables this should be good enough.
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.