我有以下程序,我通过引用函数myFunc
传递一个向量,在这个函数中,我向向量中添加了一些元素。
我没有释放使用new的对象创建,因为现在忽略内存泄漏。
myFunc()
执行完成后,我打印变量ctor和dtor
知道构造函数和析构函数被调用了多少次。
输出是:
Before Exiting 5 7
我创建了5个对象,因此ctor
为5
。但为什么dtor
7
?额外的两个计数从何而来?我错过了什么吗?
#include
#include
using namespace std;
static int ctor = 0;
static int dtor = 0;
class MyClass
{
public:
MyClass(int n)
{
i = n;
ctor++;
// cout << "Myclass ctor " << ctor << endl;
}
~MyClass()
{
dtor++;
// cout << "Myclass dtor" << dtor << endl;
}
private:
int i;
};
void myFunc(vector<MyClass> &m);
void myFunc(vector<MyClass> &m)
{
MyClass *mc;
for(int i = 0; i < 5; i++)
{
mc = new MyClass(i);
m.push_back(*mc);
}
}
int main()
{
vector<MyClass> m;
vector<MyClass>::iterator it;
myFunc(m);
cout << "Before Exiting " << ctor << " " << dtor << endl;
}
答案 0 :(得分:8)
向量复制对象,但只有int
构造函数递增ctor
。这不是考虑复制构造的对象,并且因为你没有提供,所以编译器为你提供了它。
添加
MyClass(const MyClass& rhs) i(rhs.i) { ++ctor; }
到你的班级看看是否能平衡计数。
答案 1 :(得分:2)
矢量以较小的尺寸开始。当你将一个元素推入它们时,它们会使用复制构造函数复制它,这样你就不会看到正常的构造函数被调用了。当矢量大小超出其限制时,它会将其限制增加其当前大小的倍数(例如,将其加倍)。
保证向量始终将对象保留在连续的内存中,因此如果添加新对象超过向量capacity()(即size()+ 1&gt; capacity()),向量会在某处分配新内存并复制所有元素进去。这将再次使用复制构造函数。因此,来自矢量预调整大小的元素将在使用其复制构造函数将它们复制到新分配的空间后调用它们的析构函数。
因此,比普通构造函数调用更多的析构函数调用:)
答案 2 :(得分:1)
向量有时会调用另一个构造函数复制构造函数,它是由编译器为您的类隐式生成的。这就是为什么缺少一些ctor++
调用的原因:并非所有对象都是用你定义的构造函数构造的,有些是用另一个构造的。
要确保带向量的类型(类)的正确行为,必须为它实现复制构造函数:
MyClass(const MyClass& rhs) { i = rhs.i; ++ctor; } // copy constructor
...因为编译器生成的那个什么也没做。
答案 3 :(得分:1)
正如其他人所指出的,结果背后的原因是复制构造函数和vector
的大小调整。 vector
具有大小和容量。当vector
必须调整大小以容纳新元素时,容量通常总是加倍,这样就不必经常调整大小。
添加一些跟踪代码以打印每个push_back
之间的向量容量,可以更清楚地了解此行为。
m.capacity(): 0
m.capacity(): 1
m.capacity(): 2
m.capacity(): 4
m.capacity(): 4
m.capacity(): 8
Before Exiting 5 7
这里实际发生的事情是,调用析构函数的唯一时间是向量调整大小时(请参阅下面的原因)。它第一次调整大小时没有元素,因此从不调用析构函数。第二次,容量为1,因此析构函数被调用一次。它第三次被调用两次,第四次被调用四次。正如计数器所示,这总共被调用七次。
在myFunc
中动态分配的元素永远不会被释放,因此析构函数永远不会在那里运行,最终的打印输出(“退出前......”)在离开{{1}的范围之前完成}被分配,因此在该打印输出之前不会调用最后一次“向量再生”的析构函数。因此,vector
的析构函数仅在向量调整大小时调用。
答案 4 :(得分:0)
§12.8.8的C ++标准说:If the class definition does not explicitly declare a copy constructor, there is no user-declared move constructor, and there is no user-declared move assignment operator, a copy constructor is implicitly declared as defaulted (8.4.2). Such an implicit declaration is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.
基本上,由于你的结构违反了rule of five,编译器为你做了一个复制构造函数和赋值运算符。其他构造函数不会增加ctor,而是使用您定义的析构函数。然后该向量使用该备用构造函数,作为速度改进。
如果将protected: MyClass(const MyClass& b);
添加到类声明中,则此问题就会消失。