4

我只是对使用变量向量与使用动态内存的指针向量的区别感到好奇,我发现了一些让我感到困惑的东西。我有一个看起来像这样的简单 main.cpp,

#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;
}

我得到一个看起来像这样的打印输出:

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 . . .

什么以及为什么所有这些破坏都发生在正常变量列表中?

4

3 回答 3

3

在专注于构建/破坏/复制(以及最终优化)的动态之前,您似乎没有意识到一个考虑因素:值不是多态的

如果B源自A

B b;
A a(b);

不会a复制b. 它只会复制到abA组件中。

与值不同,指针和引用是多态的

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

实际上会导致 pa 指向 b 的 A 子组件,但pbandpb2指向同一个b.

也就是说, avector<A>包含 A values,因此,

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

将导致:

  • 创建一个空的v
  • 创建一个临时 B();
  • 使 v 足够大以包含 A
  • 在 v.end() 处创建一个从临时 B 的 A 子组件复制的 A。
  • 销毁临时B

并且 - 在函数结束时,

  • 破坏 v (从而破坏其中的 A)

现在内存是干净的。

如果使用指针:

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

将导致:

  • 创建一个空v
  • 在堆上创建一个 B
  • 放大 v 以包含 A*
  • 将 B 的地址转换为其 A 的子组件地址(对于单一继承,它们很可能是相同的)
  • 在 v.end() 处创建从 B 的转换地址复制的 A*(注意您正在转换指针,而不是对象)。
  • 摧毁 v
  • 销毁其中的 A*。
  • 堆上的 B 还在(内存泄漏,因为没有其他方法可以访问它来删除它)

为避免泄漏,您应该:

  • 在堆栈上创建 B,并获取其地址或...
  • 使用 astd::unique_ptr<A>而不是A*在向量中(这样,在向量销毁时,unique_ptr 被销毁,并且其析构函数销毁指向的 A 子对象,即具有虚拟析构函数将导致 B 的销毁。

下面的代码可以对上述问题进行更有效的演示:

// 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;
}

它会像这样输出

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
于 2012-10-31T10:14:40.533 回答
1

所有这些破坏都发生在正常变量列表中,因为

    list.push_back( B() );

将在向量内分配一个新对象并使用赋值运算符复制一个 in 参数(请参阅std::vector 是否使用其值类型的赋值运算符来 push_back 元素?)。您用作参数的那个是临时的,因此在创建后将被销毁。

此外,破坏Cor类型的对象B将输出两行。在这种情况B

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

当您传递指针时,它会复制指针的值,指向的对象不会被修改。

我个人认为,如果复制构造函数和赋值运算符是轻量级且已声明的,则使用值向量的开销可以忽略不计inline

于 2012-10-31T08:43:41.630 回答
0

一些东西:

  1. 您正在使用 A 对象的向量,所有这些对象,而不是指针,因此,您不能删除向量项。允许这个 y 声明一个 A 指针向量的好方法,std::vector<A *>并用v.push_back(new B());or推回它们v.push_back(new C());
  2. 如果向此指针向量添加子类,如 B 或 C,则必须确保删除所有“信息”。如果你没有在 中声明一个虚拟析构函数A,你只会删除子类信息,而不是基类之一。对于所有继承情况请记住这一点。

此外,请记住 UmNyobe 和 Luchian 的所有建议。

于 2012-10-31T08:57:28.217 回答