例如,如果我有这个类:
class Counter {
public:
int* j = new int[5];
}
指针变量被初始化为数据成员。如果在我的复制构造函数中,我有类似的东西
int* j = new int[7]
或int* j = new int[5]()
,
因此,初始化数据成员时,它是否会为第一个成员创建内存泄漏,因为它事先没有被删除?或者原始数据成员甚至不会初始化?
如果成员初始值设定项列表中没有相同的数据成员,则在构造函数中使用非静态数据成员的默认成员初始值设置项
[…]它会造成内存泄漏吗?
是。
示例中使用的默认成员初始值设定项(DMI(:
如果对于给定构造函数,数据成员(此处为j
(未在该给定构造函数的成员初始值设定项列表中初始化,则仅使用class Counter { public: int* j = new int[5]; // default member initializer for data member 'j' }
。
因此,如果使用无成员初始值设定项列表向Counter
添加复制构造函数,则将使用数据成员j
的默认成员初始值设置项,因此将出现内存泄漏。
我们可以通过将数据成员j
的DMI更改为立即调用的lambda来研究这种行为,以允许我们跟踪何时使用DMI,以及一个伪复制ctor,它只是通过不同的方式复制参数中副本的指针(这只是这个伪示例;请参阅最后一段关于生命周期管理以及深度复制与浅层复制(:
#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.
}
或者简单地将数据成员CCD_ 8显式地设置为CCD_
#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.
}
您将覆盖数据成员CCD_ 10的DMI。
请注意,在使用原始C风格指针实现手动内存管理时,您需要格外小心,这是导致终身问题的常见原因。如果可能,请改用智能指针,如std::unique_pointer
或std::shared_pointer
,以避免使用寿命问题;然而,这超出了这个问题的范围。还要注意的是,在上面设计的示例中,复制构造函数将浅层复制参数中副本的j
数据成员指针指向(并且可能拥有(的int
资源。为了实现真实案例的复制构造函数,您可能需要深入复制此资源。
如果没有覆盖,则会触发构造实例化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];
}
默认情况下,复制构造函数通过复制j
来覆盖它。
因此,如果您显式地编写类似的复制构造函数
Counter(const Counter& c) : j(c.j) {};
它将正常工作。但如果你写得像
Counter(const Counter& c) {j=c.j;};
它将导致内存泄漏。