使用复制构造函数时,类数据成员是否在复制构造函数之前初始化



例如,如果我有这个类:

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_pointerstd::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;};

它将导致内存泄漏。

最新更新