Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I have polymorphic containers with value semantics in C++?

Tags:

c++

stl

As a general rule, I prefer using value rather than pointer semantics in C++ (ie using vector<Class> instead of vector<Class*>). Usually the slight loss in performance is more than made up for by not having to remember to delete dynamically allocated objects.

Unfortunately, value collections don't work when you want to store a variety of object types that all derive from a common base. See the example below.

#include <iostream>  using namespace std;  class Parent {     public:         Parent() : parent_mem(1) {}         virtual void write() { cout << "Parent: " << parent_mem << endl; }         int parent_mem; };  class Child : public Parent {     public:         Child() : child_mem(2) { parent_mem = 2; }         void write() { cout << "Child: " << parent_mem << ", " << child_mem << endl; }          int child_mem; };  int main(int, char**) {     // I can have a polymorphic container with pointer semantics     vector<Parent*> pointerVec;      pointerVec.push_back(new Parent());     pointerVec.push_back(new Child());      pointerVec[0]->write();      pointerVec[1]->write();       // Output:     //     // Parent: 1     // Child: 2, 2      // But I can't do it with value semantics      vector<Parent> valueVec;      valueVec.push_back(Parent());     valueVec.push_back(Child());    // gets turned into a Parent object :(      valueVec[0].write();         valueVec[1].write();          // Output:     //      // Parent: 1     // Parent: 2  } 

My question is: Can I have have my cake (value semantics) and eat it too (polymorphic containers)? Or do I have to use pointers?

like image 310
AndrewR Avatar asked Sep 03 '08 02:09

AndrewR


2 Answers

Since the objects of different classes will have different sizes, you would end up running into the slicing problem if you store them as values.

One reasonable solution is to store container safe smart pointers. I normally use boost::shared_ptr which is safe to store in a container. Note that std::auto_ptr is not.

vector<shared_ptr<Parent>> vec; vec.push_back(shared_ptr<Parent>(new Child())); 

shared_ptr uses reference counting so it will not delete the underlying instance until all references are removed.

like image 166
1800 INFORMATION Avatar answered Sep 30 '22 02:09

1800 INFORMATION


I just wanted to point out that vector<Foo> is usually more efficient than vector<Foo*>. In a vector<Foo>, all the Foos will be adjacent to each other in memory. Assuming a cold TLB and cache, the first read will add the page to the TLB and pull a chunk of the vector into the L# caches; subsequent reads will use the warm cache and loaded TLB, with occasional cache misses and less frequent TLB faults.

Contrast this with a vector<Foo*>: As you fill the vector, you obtain Foo*'s from your memory allocator. Assuming your allocator is not extremely smart, (tcmalloc?) or you fill the vector slowly over time, the location of each Foo is likely to be far apart from the other Foos: maybe just by hundreds of bytes, maybe megabytes apart.

In the worst case, as you scan through a vector<Foo*> and dereferencing each pointer you will incur a TLB fault and cache miss -- this will end up being a lot slower than if you had a vector<Foo>. (Well, in the really worst case, each Foo has been paged out to disk, and every read incurs a disk seek() and read() to move the page back into RAM.)

So, keep on using vector<Foo> whenever appropriate. :-)

like image 37
0124816 Avatar answered Sep 30 '22 03:09

0124816