class 数据成员在使用拷贝构造函数时是否先于拷贝构造函数初始化?

Are class data members initialized before the copy constructor when using the copy constructor?

例如,如果我有这个 class:

class Counter {
public:
    int* j = new int[5];
}

指针变量被初始化为数据成员。如果在我的复制构造函数中,我有类似

int* j = new int[7]int* j = new int[5](),

因此还要初始化数据成员,是否会为第一个成员造成内存泄漏,因为它没有事先删除?还是原始数据成员连初始化都没有?

non-static 数据成员的默认成员初始值设定项将用于成员初始值设定项列表中不存在相同数据成员的构造函数

[...] will it create a memory leak ... ?

是的。

A 默认成员初始值设定项 (DMI),如您示例中所用:

class Counter {
public:
    int* j = new int[5];  // default member initializer for data member 'j'
}

仅用于如果对于给定的构造函数,此处j的数据成员未在成员初始化列表中初始化 给定的构造函数。

因此,如果将复制构造函数添加到 Counter 没有成员初始值设定项列表 ,则数据成员 j 的默认成员初始值设定项将为使用,因此您将有内存泄漏。

我们可以通过将数据成员 j 的 DMI 更改为立即调用的 lambda 来研究此行为,以允许我们跟踪何时使用或不使用 DMI,以及一个虚拟复制函数通过不同的方式简单地复制 copy-in 参数的指针(这只是这个虚拟示例;参见关于生命周期管理的最后一段以及 deep-copying 与 shallow-copying):

#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 作为复制构造函数中成员初始值设定项列表的一部分:

#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-style 指针实施手动内存管理时需要格外小心,这是生命周期问题的一个非常常见的方法。如果可能,改为依赖 std::unique_pointerstd::shared_pointer 等智能指针来避免生命周期问题;然而,这超出了这个问题的范围。请注意,在上面的人为示例中,复制构造函数将是 shallow-copying int 资源,j 数据成员指针指向该资源copy-in 参数点(并且可能拥有)。要实现真实案例的复制构造函数,您可能需要 deep-copy 此资源。

构造实例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;};

会造成内存泄漏