C++拷贝构造函数为什么要用引用参数?| 核心概念详解

C++拷贝构造函数为什么要用引用参数? #

训练营里有同学问:

img

那拷贝构造函数不加引用会怎么样呢?

贴段代码:

#include <iostream>

class A {
public:
    A() {
        std::cout << "A \n";
    }
    ~A() {
        std::cout << "~A \n";
    }
    // A(const A& a) {
    //     std::cout << "A(const& A) \n";
    // }
    A(const A a) {
        std::cout << "A(const A) \n";
    }
    A& operator=(const A& a) {
        if (&a == this) {
            return *this;
        }
        std::cout << "operator= \n";
        return *this;
    }
};

编译结果如图:

img

可以看到,如果拷贝构造函数不加引用,编译都会失败的,那为什么编译器要这样限制,为什么一定要加引用呢?

拷贝构造函数的参数必须是引用,因为如果不是,我们将不得不通过值传递对象。通过值传递对象将需要拷贝对象,这将调用拷贝构造函数。

这会导致拷贝构造函数调用的无限循环,而通过使用引用,就可以避免这个无限循环,因为引用不会创建新对象,而是指向现有对象的内存位置。

考虑以下带有接受引用参数的拷贝构造函数的类:

class MyClass {
public:
    int a;
    // 常规构造函数
    MyClass(int value) : a(value) {}
    // 接受引用参数的拷贝构造函数
    MyClass(const MyClass &obj) {
        a = obj.a;
    }
};

int main() {
    MyClass original(5); // 调用常规构造函数
    MyClass copy(original); // 调用接受引用参数的拷贝构造函数
}

在这个例子中,创建 copy 对象时,调用了拷贝构造函数,并且它接受对 original 对象的引用。这避免了无限循环问题,因为引用简单地指向现有对象的内存位置,并且在过程中没有创建新对象。

如果不加引用:

MyClass(MyClass obj) {
    a = obj.a;
}

这将在拷贝时导致无限循环,因为:

  1. 通过值传递时会创建一个新对象。
  2. 创建新对象需要调用拷贝构造函数
  3. 调用拷贝构造函数需要通过值传递对象,导致步骤1。

通过在拷贝构造函数中使用引用参数,可以防止这种无限循环,因为在传递引用时不会创建新对象。

所以编译器直接在源头就规避了这种问题,防止我们多踩坑。

总结 #

拷贝构造函数的参数必须是引用,因为如果不是,我们将不得不通过值传递对象。通过值传递对象将需要拷贝对象,这将调用拷贝构造函数。

这会导致拷贝构造函数调用的无限循环,而通过使用引用,就可以避免这个无限循环,因为引用不会创建新对象,而是指向现有对象的内存位置。