我只是对使用变量向量与动态内存指针向量的差异感到好奇,而且我发现了一些令我困惑的东西。我有一个简单的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 . . .
所有这些破坏在普通变量列表中出现的原因和原因是什么?
答案 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());
; 并且 - 在功能结束时,
记忆现在很干净。
如果使用指针:
v
将导致:
vector<A*> v;
v.push_back(new 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?)。您用作参数的那个是临时的,因此在创建后将被销毁。
此外,对C
或B
类型的对象进行输出将输出两行。在B
的情况下,它将是
I'm a dead B
I'm a dead A
当你传递指针时,它会复制指针的值,指向的对象不会被修改。
我个人认为如果复制构造函数和赋值运算符是轻量级的并且声明为inline
,则使用值向量的开销可以忽略不计。
答案 2 :(得分:0)
一些事情:
std::vector<A *>
的向量并使用v.push_back(new B());
或v.push_back(new C());
A
中声明虚拟析构函数,则只会删除子类信息,而不会删除基类信息。请记住这适用于所有继承情况。此外,请记住UmNyobe和Luchian的所有建议。