变量向量与指针向量

时间:2012-10-31 08:40:24

标签: c++ visual-studio-2010

我只是对使用变量向量与动态内存指针向量的差异感到好奇,而且我发现了一些令我困惑的东西。我有一个简单的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 . . .

所有这些破坏在普通变量列表中出现的原因和原因是什么?

3 个答案:

答案 0 :(得分:3)

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

如果B来自A

B b;
A a(b);

不会a b的副本。它只会复制a b的{​​{1}}子组件。

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

A

实际上会导致pa指向b的A子组件,但B b; B* pb = &b; A* pa = pb; B* pb2 = const_cast<B*>(pa); pb指向相同的pb2

也就是说,b包含A vector<A>,因此

values

将导致:

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

并且 - 在功能结束时,

  • 摧毁v(从而摧毁其中的A)

记忆现在很干净。

如果使用指针:

v

将导致:

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

为避免泄漏,您应该:

  • 在堆栈上创建B,并获取其地址或......
  • 在向量中使用v而不是std::unique_ptr<A>(这样,在向量销毁时,unique_ptr被销毁,其析构函数会破坏指向的A子对象,因此会产生虚拟析构函数在B的毁灭中。

以下代码可以更有效地论证上述问题:

A*

输出如

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

答案 1 :(得分:1)

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

    list.push_back( B() );

将在向量内部分配一个新对象,并使用赋值运算符复制参数中的对象(请参阅Does std::vector use the assignment operator of its value type to push_back elements?)。您用作参数的那个是临时的,因此在创建后将被销毁。

此外,对CB类型的对象进行输出将输出两行。在B的情况下,它将是

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

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

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

答案 2 :(得分:0)

一些事情:

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