Vectors of variables vs vector of pointers
c++, visual-studio-2010
Solution
Before concentrating on the dynamics of contructing / destructing / copy (and eventual optimization), there is a consideration you seem not aware of: values are not polymorphic.
If `B` derives from `A`,
B b;
A a(b);
will not make `a` a copy of `b`. It will just copy in `a` the `b`'s `A` subcomponent.
Unlike values, pointer and references are polymorphic:
B b;
B* pb = &b;
A* pa = pb;
B* pb2 = const_cast<B*>(pa);
will in fact result in pa pointing into b's A subcomponent, but `pb` and `pb2` to point to the same `b`.
That said, a `vector<A>` contains A `values`, hence,
vecotr<A> v;
v.push_back(B());
will result in:
- Create an empty `v`;
- Create a temporary B();
- Make v large enough to contain an A
- Create at v.end() an A copied from the temporary B's A subcomponent.
- Destroy the temporary B
And - at the end of the function,
- destroy v (and thus destroy the A inside it)
The memory is now clean.
If using pointers:
vector<A*> v;
v.push_back(new B());
will result in:
- Create an empty `v`
- Create a B on the heap
- Enlarge v to contain an A*
- Convert the B's address into its A's subcomponent address (for single inheritance they will most likely be the same)
- Create at v.end() a A* copied from the B's converted address (note you are converting pointers, not objects).
- Destroy v
- Destroy the A* in it.
- The B on the heap is still there (memory leak, since there is no other way to access it to delete it)
To avoid the leak you should either:
- Create B on the stack, and get its address or...
- Use a `std::unique_ptr<A>` instead of `A*` in vector (so that, at vector destruction, the unique_ptr is destroyed and its destructor destroy the pointed A sub-object, that, having a virtual destructor will result in B's destruction.
A more effective demonstration on the above issue can be given by the following code:
// Compile as g++ -pedantic -Wall -std=c++11
#include <vector>
#include <list>
#include <iostream>
class A
{
public:
A() { std::cout << "- creating A at " << this << std::endl; }
A(const A& a) { std::cout << "- creating A at " << this << " from " << &a << std::endl; }
A& operator=(const A& a) { std::cout << "- assigning A at " << this << " from " << &a << std::endl; return *this; }
virtual ~A() { std::cout << "- destroying A at " << this << std::endl; }
virtual void hello() const { std::cout << "- A's hello from " << this << std::endl; }
};
class B: public A
{
public:
B() { std::cout << "- creating B at " << this << std::endl; }
B(const B& a) { std::cout << "- creating B at " << this << " from " << &a << std::endl; }
B& operator=(const B& a) { std::cout << "- assigning B at " << this << " from " << &a << std::endl; return *this; }
virtual ~B() { std::cout << "- destroying B at " << this << std::endl; }
virtual void hello() const { std::cout << "- B's hello from " << this << std::endl; }
};
class C: public A
{
public:
C() { std::cout << "- creating C at " << this << std::endl; }
C(const C& a) { std::cout << "- creating C at " << this << " from " << &a << std::endl; }
C& operator=(const C& a) { std::cout << "- assigning C at " << this << " from " << &a << std::endl; return *this; }
virtual ~C() { std::cout << "- destroying C at " << this << std::endl; }
virtual void hello() const { std::cout << "- C's hello from " << this << std::endl; }
};
int main()
{
std::cout << "creating some objects" << std::endl;
A a1, a2;
B b1, b2;
C c1, c2;
{
std::cout << "operating with values" << std::endl;
std::vector<A> valvect;
valvect.push_back(a1);
valvect.push_back(a1);
valvect.push_back(b1);
valvect.push_back(b1);
valvect.push_back(c1);
valvect.push_back(c1);
valvect.push_back(a2);
valvect.push_back(a2);
valvect.push_back(b2);
valvect.push_back(b2);
valvect.push_back(c2);
valvect.push_back(c2);
for(const auto& x: valvect) x.hello();
std::cout << "at '}' destroy the value vector" << std::endl;
}
{
std::cout << "operating with pointers" << std::endl;
std::vector<A*> ptrvect;
ptrvect.push_back(&a1);
ptrvect.push_back(&a1);
ptrvect.push_back(&b1);
ptrvect.push_back(&b1);
ptrvect.push_back(&c1);
ptrvect.push_back(&c1);
ptrvect.push_back(&a2);
ptrvect.push_back(&a2);
ptrvect.push_back(&b2);
ptrvect.push_back(&b2);
ptrvect.push_back(&c2);
ptrvect.push_back(&c2);
for(const auto& x: ptrvect)
x->hello();
std::cout << "at '}' destroy the pointer's vector" << std::endl;
}
{
std::cout << "operating with list of values" << std::endl;
std::list<A> vallst;
vallst.push_back(a1);
vallst.push_back(a1);
vallst.push_back(b1);
vallst.push_back(b1);
vallst.push_back(c1);
vallst.push_back(c1);
vallst.push_back(a2);
vallst.push_back(a2);
vallst.push_back(b2);
vallst.push_back(b2);
vallst.push_back(c2);
vallst.push_back(c2);
for(const auto& x: vallst)
x.hello();
std::cout << "at '}' destroy the value list" << std::endl;
}
{
std::cout << "operating with list of pointers" << std::endl;
std::list<A*> ptrlst;
ptrlst.push_back(&a1);
ptrlst.push_back(&a1);
ptrlst.push_back(&b1);
ptrlst.push_back(&b1);
ptrlst.push_back(&c1);
ptrlst.push_back(&c1);
ptrlst.push_back(&a2);
ptrlst.push_back(&a2);
ptrlst.push_back(&b2);
ptrlst.push_back(&b2);
ptrlst.push_back(&c2);
ptrlst.push_back(&c2);
for(const auto& x: ptrlst)
x->hello();
std::cout << "at '}' destroy the pointer's list" << std::endl;
}
std::cout << "now finally at '};' destroy the objects created at the beginning" << std::endl;
return 0;
}
It will output like
creating some objects
- creating A at 0x22febc
- creating A at 0x22feb8
- creating A at 0x22feb4
- creating B at 0x22feb4
- creating A at 0x22feb0
- creating B at 0x22feb0
- creating A at 0x22feac
- creating C at 0x22feac
- creating A at 0x22fea8
- creating C at 0x22fea8
operating with values
- creating A at 0x3e3eb8 from 0x22febc
- creating A at 0x3e2434 from 0x22febc
- creating A at 0x3e2430 from 0x3e3eb8
- destroying A at 0x3e3eb8
- creating A at 0x3e2448 from 0x22feb4
- creating A at 0x3e2440 from 0x3e2430
- creating A at 0x3e2444 from 0x3e2434
- destroying A at 0x3e2430
- destroying A at 0x3e2434
- creating A at 0x3e244c from 0x22feb4
- creating A at 0x3e2468 from 0x22feac
- creating A at 0x3e2458 from 0x3e2440
- creating A at 0x3e245c from 0x3e2444
- creating A at 0x3e2460 from 0x3e2448
- creating A at 0x3e2464 from 0x3e244c
- destroying A at 0x3e2440
- destroying A at 0x3e2444
- destroying A at 0x3e2448
- destroying A at 0x3e244c
- creating A at 0x3e246c from 0x22feac
- creating A at 0x3e2470 from 0x22feb8
- creating A at 0x3e2474 from 0x22feb8
- creating A at 0x3e24a0 from 0x22feb0
- creating A at 0x3e2480 from 0x3e2458
- creating A at 0x3e2484 from 0x3e245c
- creating A at 0x3e2488 from 0x3e2460
- creating A at 0x3e248c from 0x3e2464
- creating A at 0x3e2490 from 0x3e2468
- creating A at 0x3e2494 from 0x3e246c
- creating A at 0x3e2498 from 0x3e2470
- creating A at 0x3e249c from 0x3e2474
- destroying A at 0x3e2458
- destroying A at 0x3e245c
- destroying A at 0x3e2460
- destroying A at 0x3e2464
- destroying A at 0x3e2468
- destroying A at 0x3e246c
- destroying A at 0x3e2470
- destroying A at 0x3e2474
- creating A at 0x3e24a4 from 0x22feb0
- creating A at 0x3e24a8 from 0x22fea8
- creating A at 0x3e24ac from 0x22fea8
- A's hello from 0x3e2480
- A's hello from 0x3e2484
- A's hello from 0x3e2488
- A's hello from 0x3e248c
- A's hello from 0x3e2490
- A's hello from 0x3e2494
- A's hello from 0x3e2498
- A's hello from 0x3e249c
- A's hello from 0x3e24a0
- A's hello from 0x3e24a4
- A's hello from 0x3e24a8
- A's hello from 0x3e24ac
at '}' destroy the value vector
- destroying A at 0x3e2480
- destroying A at 0x3e2484
- destroying A at 0x3e2488
- destroying A at 0x3e248c
- destroying A at 0x3e2490
- destroying A at 0x3e2494
- destroying A at 0x3e2498
- destroying A at 0x3e249c
- destroying A at 0x3e24a0
- destroying A at 0x3e24a4
- destroying A at 0x3e24a8
- destroying A at 0x3e24ac
operating with pointers
- A's hello from 0x22febc
- A's hello from 0x22febc
- B's hello from 0x22feb4
- B's hello from 0x22feb4
- C's hello from 0x22feac
- C's hello from 0x22feac
- A's hello from 0x22feb8
- A's hello from 0x22feb8
- B's hello from 0x22feb0
- B's hello from 0x22feb0
- C's hello from 0x22fea8
- C's hello from 0x22fea8
at '}' destroy the pointer's vector
operating with list of values
- creating A at 0x3e2448 from 0x22febc
- creating A at 0x3e24d0 from 0x22febc
- creating A at 0x3e24e8 from 0x22feb4
- creating A at 0x3e2500 from 0x22feb4
- creating A at 0x3e2518 from 0x22feac
- creating A at 0x3e2530 from 0x22feac
- creating A at 0x3e2548 from 0x22feb8
- creating A at 0x3e2560 from 0x22feb8
- creating A at 0x3e2578 from 0x22feb0
- creating A at 0x3e2590 from 0x22feb0
- creating A at 0x3e25a8 from 0x22fea8
- creating A at 0x3e25c0 from 0x22fea8
- A's hello from 0x3e2448
- A's hello from 0x3e24d0
- A's hello from 0x3e24e8
- A's hello from 0x3e2500
- A's hello from 0x3e2518
- A's hello from 0x3e2530
- A's hello from 0x3e2548
- A's hello from 0x3e2560
- A's hello from 0x3e2578
- A's hello from 0x3e2590
- A's hello from 0x3e25a8
- A's hello from 0x3e25c0
at '}' destroy the value list
- destroying A at 0x3e2448
- destroying A at 0x3e24d0
- destroying A at 0x3e24e8
- destroying A at 0x3e2500
- destroying A at 0x3e2518
- destroying A at 0x3e2530
- destroying A at 0x3e2548
- destroying A at 0x3e2560
- destroying A at 0x3e2578
- destroying A at 0x3e2590
- destroying A at 0x3e25a8
- destroying A at 0x3e25c0
operating with list of pointers
- A's hello from 0x22febc
- A's hello from 0x22febc
- B's hello from 0x22feb4
- B's hello from 0x22feb4
- C's hello from 0x22feac
- C's hello from 0x22feac
- A's hello from 0x22feb8
- A's hello from 0x22feb8
- B's hello from 0x22feb0
- B's hello from 0x22feb0
- C's hello from 0x22fea8
- C's hello from 0x22fea8
at '}' destroy the pointer's list
now finally at '};' destroy the objects created at the beginning
- destroying C at 0x22fea8
- destroying A at 0x22fea8
- destroying C at 0x22feac
- destroying A at 0x22feac
- destroying B at 0x22feb0
- destroying A at 0x22feb0
- destroying B at 0x22feb4
- destroying A at 0x22feb4
- destroying A at 0x22feb8
- destroying A at 0x22febc
Problem
I was just curious about the differences of using vectors of variables vs a vector of pointers with dynamic memory, and I've found something that confused me. I have a simple main.cpp that looks like this, ``` #include <iostream> #include <vector> using namespace std; class A { public: A() { x = 2;} virtual ~A() { cout << "I'm a dead A\n";} public: int x; }; class B : public A { public: B() {x = 4;} ~B() { cout << "I'm a dead B\n";} }; class C : public A { public: C() { x = 6;} ~C() { cout << "I'm a dead C\n";} }; int main() { cout << "Starting variable list\n"; std::vector<A> list; list.push_back( B() ); list.push_back( A() ); list.push_back( B() ); list.push_back( C() ); list.push_back( A() ); for(std::vector<A>::iterator it = list.begin(); it != list.end(); it++) { cout << it->x << endl; } cout << "\n\nStarting pointer list\n"; std::vector<A *> ptrList; ptrList.push_back( new B()); ptrList.push_back( new A()); ptrList.push_back( new B()); ptrList.push_back( new C()); ptrList.push_back( new A()); for(std::vector<A *>::iterator it = ptrList.begin(); it != ptrList.end(); it++) { cout << (*it)->x << endl; } for(std::vector<A *>::iterator it = ptrList.begin(); it != ptrList.end(); it++) { delete *it; } system("PAUSE"); return 0; } ``` And I get a printout that looks like this: ``` Starting variable list I'm a dead B I'm a dead A I'm a dead A I'm a dead A I'm a dead A I'm a dead A I'm a dead B I'm a dead A I'm a dead A I'm a dead A I'm a dead A I'm a dead C I'm a dead A I'm a dead A I'm a dead A I'm a dead A I'm a dead A I'm a dead A 4 2 4 6 2 Starting pointer list 4 2 4 6 2 I'm a dead B I'm a dead A I'm a dead A I'm a dead B I'm a dead A I'm a dead C I'm a dead A I'm a dead A Press any key to continue . . . ``` What and why did all those destructions occurr in the normal variable list?