提问人:diagoot 提问时间:10/27/2020 更新时间:10/27/2020 访问量:99
使用 copy 构造函数时,是否在复制构造函数之前初始化类数据成员?
Are class data members initialized before the copy constructor when using the copy constructor?
问:
例如,如果我有这个类:
class Counter {
public:
int* j = new int[5];
}
指针变量初始化为数据成员。如果在我的复制构造函数中,我有类似的东西
int* j = new int[7]
或int* j = new int[5]()
因此,初始化数据成员,是否会为第一个成员造成内存泄漏,因为它没有事先删除?还是原始数据成员甚至不会初始化?
答:
非静态数据成员的默认成员初始值设定项将在成员初始值设定项列表中不存在相同数据成员的构造函数中使用
[...]它会造成内存泄漏吗?
是的。
示例中使用的默认成员初始值设定项 (DMI):
class Counter { public: int* j = new int[5]; // default member initializer for data member 'j' }
仅当对于给定构造函数时,数据成员 here 未在该给定构造函数的成员初始值设定项列表中初始化。j
因此,如果将复制构造函数添加到没有成员初始值设定项列表,则将使用数据成员的默认成员初始值设定项,因此将发生内存泄漏。Counter
j
我们可以通过将数据成员的 DMI 更改为立即调用的 lambda 来研究这种行为,以允许我们跟踪何时使用 DMI,以及一个虚拟复制 ctor,它只是通过不同的方式复制复制 copy-in 参数的指针(这只是针对这个虚拟示例;请参阅关于生命周期管理以及深度复制与浅复制的最后一段):j
#include <iostream>
struct Counter {
int* j = []() {
std::cout << "Calling DMI for j.\n";
auto p = new int[5];
return p; }();
// Uses the DMI for member 'j'.
Counter() {}
// Uses the DMI for member 'j'.
Counter(const Counter& c) { j = c.j; } // Memory leak.
};
int main() {
Counter c1; // Calling DMI for j.
Counter c2 = c1; // Calling DMI for j.
// Delete resource common for c1 and c2.
delete c2.p; // A rogue resource from c2 construction was leaked.
}
如果任一执行复制到复制构造函数的成员初始值设定项列表中的数据成员:j
#include <iostream>
class Counter {
public:
int* j = []() {
std::cout << "Calling DMI for j.\n";
auto p = new int[5];
return p; }();
// Uses the DMI for member 'j'.
Counter() {}
// Does not use the DMI for data member 'j'.
Counter(const Counter& c) : j(c.j) { }
};
int main() {
Counter c1; // Calling DMI for j.
Counter c2 = c1;
// Delete resource common for c1 and c2.
delete c2.p; // OK, no resources leaked.
}
或者干脆将数据成员显式设置为复制 CTOR 中成员初始值设定项列表的一部分:j
nullptr
#include <iostream>
class Counter {
public:
int* j = []() {
std::cout << "Calling DMI for j.\n";
auto p = new int[5];
return p; }();
// Uses the DMI for member 'j'.
Counter() {}
// Does not use the DMI for data member 'j'.
Counter(const Counter& c) : j(nullptr) { j = c.j; }
};
int main() {
Counter c1; // Calling DMI for j.
Counter c2 = c1;
// Delete resource common for c1 and c2.
delete c2.p; // OK, no resources leaked.
}
您将覆盖数据成员的 DMI。j
请注意,在使用原始 C 样式指针实现手动内存管理时,需要格外小心,这是生存期问题的常见原因。如果可能,请改用智能指针,例如 或 以避免生存期问题;然而,这超出了这个问题的范围。另请注意,在上面的人为示例中,复制构造函数将浅层复制复制参数的数据成员指针指向(并且可能拥有的)资源。若要实现实际案例复制构造函数,可能需要深度复制此资源。std::unique_pointer
std::shared_pointer
int
j
如果没有覆盖,将触发构造实例化。int* j = new int[5];
让我举个例子:
class Counter {
public:
Counter(int x) {}; // j will be initialized as int* j = new int[5];
Counter(double y) : j(nullptr) {}; // the line j = new int[5]; won't be invoked. instead j = nullptr;
int* j = new int[5];
}
默认情况下,copy 构造函数通过复制 来覆盖它。j
因此,如果您显式编写复制构造函数,例如
Counter(const Counter& c) : j(c.j) {};
它将正常工作。但是如果你把它写成
Counter(const Counter& c) {j=c.j;};
这将导致内存泄漏。
评论