C++ Polymorphism Without Pointers

C++ polymorphism without pointers

Ultimately, no.

Polymorphism only works with non-value types: references and pointers. And since references can only be bound once, you cannot really use them in standard containers. That leaves you with pointers.

You're attacking the problem at the wrong end. If you are concerned about the overhead of allocating lots of small objects (and I'm assuming that this is a legitimate concern. That is, you have actual profiling data or sufficient experience to know it is a concern for your specific application), then you should fix that. Change how you're allocating memory for these objects. Make a small allocation heap or something.

Admittedly, pre-C++0x's allocators are somewhat lacking in this regard, since they have to be stateless. But for your purposes, you should be able to deal with it.


From your edit:

That is a terrible idea. Erasing from a std::deque at anywhere but the start or end will invalidate every pointer in your std::list.

Given your comment, this idea is functional. However, having all of these different memory blocks for different kinds of objects seems to go against the whole point of inheritance. After all, you can't just write a new type of Animal and slip it into the std::list; you have to provide memory management for it.

Are you sure that inheritance-based polymorphism is what you need here? Are you sure that some other methodology would not work just as well?

Does polymorphism work in C++ without pointers / references?

No it will not be possible without pointers.

Since you create an object B and push it to the vector containing A, it will get copied (sent to the copy constructor of A) and an instance of A will be added to the vector. I.e. the object will be sliced.

Given this code:

struct A {
virtual void d() {
std::cout << "Hello A" << std::endl;
}
};

struct B : public A {
virtual void d() {
std::cout << "Hello B" << std::endl;
}
};

int main() {
std::vector<A> v;
v.push_back(B());
v[0].d();
}

The output will be:

Hello A

Why doesn't polymorphism work without pointers/references?

In C++, an object always has a fixed type and size known at compile-time and (if it can and does have its address taken) always exists at a fixed address for the duration of its lifetime. These are features inherited from C which help make both languages suitable for low-level systems programming. (All of this is subject to the as-if, rule, though: a conforming compiler is free to do whatever it pleases with code as long as it can be proven to have no detectable effect on any behavior of a conforming program that is guaranteed by the standard.)

A virtual function in C++ is defined (more or less, no need for extreme language lawyering) as executing based on the run-time type of an object; when called directly on an object this will always be the compile-time type of the object, so there is no polymorphism when a virtual function is called this way.

Note that this didn't necessarily have to be the case: object types with virtual functions are usually implemented in C++ with a per-object pointer to a table of virtual functions which is unique to each type. If so inclined, a compiler for some hypothetical variant of C++ could implement assignment on objects (such as Base b; b = Derived()) as copying both the contents of the object and the virtual table pointer along with it, which would easily work if both Base and Derived were the same size. In the case that the two were not the same size, the compiler could even insert code that pauses the program for an arbitrary amount of time in order to rearrange memory in the program and update all possible references to that memory in a way that could be proven to have no detectable effect on the semantics of the program, terminating the program if no such rearrangement could be found: this would be very inefficient, though, and could not be guaranteed to ever halt, obviously not desirable features for an assignment operator to have.

So in lieu of the above, polymorphism in C++ is accomplished by allowing references and pointers to objects to reference and point to objects of their declared compile-time types and any subtypes thereof. When a virtual function is called through a reference or pointer, and the compiler cannot prove that the object referenced or pointed to is of a run-time type with a specific known implementation of that virtual function, the compiler inserts code which looks up the correct virtual function to call a run-time. It did not have to be this way, either: references and pointers could have been defined as being non-polymorphic (disallowing them to reference or point to subtypes of their declared types) and forcing the programmer to come up with alternative ways of implementing polymorphism. The latter is clearly possible since it's done all the time in C, but at that point there's not much reason to have a new language at all.

In sum, the semantics of C++ are designed in such a way to allow the high-level abstraction and encapsulation of object-oriented polymorphism while still retaining features (like low-level access and explicit management of memory) which allow it to be suitable for low-level development. You could easily design a language that had some other semantics, but it would not be C++ and would have different benefits and drawbacks.

C++ std containers - polymorphism without pointers. Is it possible?

Yes, you do have to use pointers. Otherwise, attempting to put a B into a container of A results in slicing: the B gets cut down into an A (this is not limited to containers, the exact same thing happens if you do A a = B() or if you pass a B to a function expecting an A).

When you later take it back out, it's an A that has absolutely no knowledge its lineage includes an illustrious forefather of type B -- and no matter what way you look at an A, you can't make it a B.

polymorphism: calling overrided functions without pointers

When you placed Ferrari in your first list you experienced type erasure - the "GenericCar" structure was copied into the list and anything that could have identified that it was a "FerrariCar" was lost.

You need a pointer or reference to invoke polymorphic functions, have a pointer or reference gives you access to the virtual table for your object.

To have a list that could store store such car objects and be passed around to different functions you will probably want to use smart pointers so that you don't wind up with dangling pointers or memory leaks.

#include <memory>

...

list<shared_ptr<GenericCar>> cars;
cars.push_back(shared_ptr<GenericCar>(new GenericCar()));
cars.push_back(shared_ptr<GenericCar>(new FerrariCar()));
for ( shared_ptr<GenericCar> & car : cars )
car->PrintModelName();

Why we can't implement polymorphism in C++ without base class pointer or reference?

You are asking a question and providing a code example that fails but for a different reason. From the wording of your question:

Why are references/pointers required for polymorphism?

struct base {
virtual void f();
};
struct derived : public base {
virtual void f();
};
void call1( base b ) {
b.f(); // base::f
}
void call2( base &b ) {
b.f(); // derived::f
}
int main() {
derived d;
call1(d);
call2(d);
}

When you use pass-by-value semantics (or store derived elements in a base container) you are creating copies of type base of the elements of type derived. That is called slicing, as it resembles the fact that you have a derived object and you slice/cut only the base subobject from it. In the example, call1 does not work from the object d in main, but rather with a temporary of type base, and base::f is called.

In the call2 method you are passing a reference to a base object. When the compiler sees call2(d) in main it will create a reference to the base subobject in d and pass it to the function. The function performs the operation on a reference of type base that points to an object of type derived, and will call derived::f. The same happens with pointers, when you get a base * into a derived object, the object is still derived.

Why can I not pass a container of derived pointers to a function taking a container of base pointers?

_Clearly if derived are base, a container of derived is a container of base.

No. Containers of derived are not containers of base. That would break the type system. The simplest example of using a container of derived as container of base objects breaking the type system is below.

void f( std::vector<base*> & v )
{
v.push_back( new base );
v.push_back( new another_derived );
}
int main() {
std::vector<derived*> v;
f( v ); // error!!!
}

If the line marked with error was allowed by the language, then it would allow the application to insert elements that are not of type derived* into the container, and that would mean lots of trouble...

But the question was about containers of value types...

When you have containers of value types, the elements get copied into the container. Inserting an element of type derived into a container of type base will make a copy of the subobject of type base within the derived object. That is the same slicing than above. Besides that being a language restriction, it is there for a good reason, when you have a container of base objects, you have space to hold just base elements. You cannot store bigger objects into the same container. Else the compiler would not even know how much space to reserve for each element (what if we later extend with an even-bigger type?).

In other languages it may seem as this is actually allowed (Java), but it is not. The only change is in the syntax. When you have String array[] in Java you are actually writting the equivalent of string *array[] in C++. All non-primitive types are references in the language, and the fact that you do not add the * in the syntax does not mean that the container holds instances of String, containers hold references into Strings, that are more related to c++ pointers than c++ references.

Polymorphism without new

In function f objects B() or C() are both temporary, so you can only return them from f by value.

Maybe boost::variant is for you. Then you don't even need to have the method virtual or derive from a common base class.



Related Topics



Leave a reply



Submit