简体   繁体   中英

Vectors of variables vs vector of pointers

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?

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

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 .

A few things:

  1. You are using a vector of A object, all of them, not pointers so, you can't delete vector items. The good way to allow this y declaring a vector of A pointers such std::vector<A *> and push_back them with v.push_back(new B()); or v.push_back(new C());
  2. If you add to this pointers vector A subclases, like B or C, you must ensure to delete all 'information'. If you do not declare a virtual destructor in 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM