Variable vectors vs pointer vectors

I just liked the differences in using variable vectors vs dynamic memory pointer vectors, and I 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 this destruction happen on the list of ordinary variables?

+4
source share
3 answers

Before focusing on the dynamics of contructing / destructing / copy (and possible optimization), there is a consideration that you do not seem to know about: values ​​are not polymorphic .

If B comes from A ,

 B b; A a(b); 

will not make A copy of B It just copies the subcomponent B A into A

Unlike values, pointer and links are polymorphic :

 B b; B* pb = &b; A* pa = pb; B* pb2 = const_cast<B*>(pa); 

actually lead to pa pointing to b a subcomponent of b A, but pb and pb2 to point to the same B

So a vector<A> contains A values , therefore

 vecotr<A> v; v.push_back(B()); 

will result in:

  • Create an empty v ;
  • Create a temporary B ();
  • Make v large enough to contain A
  • Create in v.end () a Copied from temporary subcomponent B A.
  • Destroy Temporary B

And - at the end of the function

  • destroy v (and thereby destroy A inside it)

Now the memory is clear.

When using pointers:

 vector<A*> v; v.push_back(new B()); 

will result in:

  • Create empty v
  • Create B on the heap
  • Increase v to contain A *
  • Convert address B to its subcomponent address (for single inheritance, they are likely to be the same)
  • Create in v.end () a *, copied from B to the converted address (note that you are converting pointers, not objects).
  • Destroy v
  • Destroy A * in it.
  • There is still a heap (memory leak, since there is no other way to access it to delete it)

To avoid leakage, you should:

  • Create B on the stack and get its address or ...
  • Use std::unique_ptr<A> instead of A* in the vector (so when you destroy the vector, unique_ptr is destroyed, and its destructor destroys the selected Sub object, which has a virtual destructor, will destroy B.

A more efficient demonstration on the above question can be asked with 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 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 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 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 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 list" << std::endl; } std::cout << "now finally at '};' destroy the objects created at the beginning" << std::endl; return 0; } 

It will be displayed as

 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 hello from 0x3e2480 - A hello from 0x3e2484 - A hello from 0x3e2488 - A hello from 0x3e248c - A hello from 0x3e2490 - A hello from 0x3e2494 - A hello from 0x3e2498 - A hello from 0x3e249c - A hello from 0x3e24a0 - A hello from 0x3e24a4 - A hello from 0x3e24a8 - A 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 hello from 0x22febc - A hello from 0x22febc - B hello from 0x22feb4 - B hello from 0x22feb4 - C hello from 0x22feac - C hello from 0x22feac - A hello from 0x22feb8 - A hello from 0x22feb8 - B hello from 0x22feb0 - B hello from 0x22feb0 - C hello from 0x22fea8 - C hello from 0x22fea8 at '}' destroy the pointer 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 hello from 0x3e2448 - A hello from 0x3e24d0 - A hello from 0x3e24e8 - A hello from 0x3e2500 - A hello from 0x3e2518 - A hello from 0x3e2530 - A hello from 0x3e2548 - A hello from 0x3e2560 - A hello from 0x3e2578 - A hello from 0x3e2590 - A hello from 0x3e25a8 - A 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 hello from 0x22febc - A hello from 0x22febc - B hello from 0x22feb4 - B hello from 0x22feb4 - C hello from 0x22feac - C hello from 0x22feac - A hello from 0x22feb8 - A hello from 0x22feb8 - B hello from 0x22feb0 - B hello from 0x22feb0 - C hello from 0x22fea8 - C hello from 0x22fea8 at '}' destroy the pointer 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 
+3
source

All these destructions occurred in the list of ordinary variables because

  list.push_back( B() ); 

will select a new object inside the vector and use the assignment operator to copy the argument in the argument (see Does std :: vector use the assignment operator of its type of values ​​for push_back elements? ). The one you used as an argument is temporary, so it will be destroyed after it is created.

In addition, two lines are displayed to destroy an object of type C or B in case B it will be

 I'm a dead B I'm a dead A 

When you pass pointers, it makes a copy of the pointer value, the object that it points to does not change.

Personally, I think the overhead of using a value vector is negligible if the copy constructor and assignment operator are lightweight and declared inline .

+1
source

A few things:

  • You are using the object vector A, all of them, not pointers, so you cannot delete the elements of the vector. A good way to allow this y to declare a pointer vector A in this way is std::vector<A *> and push_back with v.push_back(new B()); or v.push_back(new C());
  • If you add vector A subclasses to this pointer, such as B or C, you must ensure that all "information" is removed. If you do not declare a virtual destructor in A , you will only remove subclass information, not the base class. Remember this for all inheritance situations.

In addition, keep in mind all recommendations from UmNyobe and Luchian.

0
source

Source: https://habr.com/ru/post/1443175/


All Articles