Reputation: 3567
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?
Upvotes: 4
Views: 982
Reputation: 20739
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:
v
;And - at the end of the function,
The memory is now clean.
If using pointers:
vector<A*> v;
v.push_back(new B());
will result in:
v
To avoid the leak you should either:
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
Upvotes: 3
Reputation: 22910
All those destructions occurred in the normal variable list because
list.push_back( B() );
will allocate a new object inside the vector and use the assignment operator to copy the one in argument (see Does std::vector use the assignment operator of its value type to push_back elements?). The one you used as argument is temporary, so will be destroyed after being created.
Furthermore Destructing an object of type C
or B
will output two lines. in the case of B
it will be
I'm a dead B
I'm a dead A
When you pass pointers it make the copy of the value of the pointer, the object pointed to is not modified.
Personally I think the overhead of using a vector of values is negligible if the copy constructor and assignment operator are lightweight and declared inline
.
Upvotes: 1
Reputation: 3089
A few things:
std::vector<A *>
and push_back them with v.push_back(new B());
or v.push_back(new C());
A
, you only will delete subclass information, not base clases one. Remember this for all inheritance situations.Besides, have in mind all recomendations from UmNyobe and Luchian.
Upvotes: 0