如何解决使用复制构造函数时,是否在复制构造函数之前初始化了类数据成员? 非静态数据成员的默认成员初始值设定项将用于在成员初始值设定项列表中不存在相同数据成员的构造函数中
例如,如果我有此类:
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' }
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.
}
或简单地将数据成员j
设置为nullptr
作为副本ctor中成员初始化程序列表的一部分:
#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.
}
您将覆盖数据成员j
的DMI。
请注意,在实现原始C风格指针方面的手动内存管理时,您需要格外小心,这是解决生命周期问题的常用方法。如果可能,请改用智能指针,例如std::unique_pointer
或std::shared_pointer
来避免生命周期问题;但是,这超出了此问题的范围。还要注意,在上述人为设计的示例中,复制构造函数将是{em> shallow-copying (复制) {@ 1}}资源,复制参数的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;};
这将导致内存泄漏。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。