为什么在C++中复制构造函数应该通过引用接受参数?

145
为什么复制构造函数的参数必须通过引用传递?
10个回答

219
因为如果不是按引用传递,就是按值传递。要做到这一点,你需要制作一份副本,并调用复制构造函数。但是要做到这一点,我们需要创建一个新值,因此我们调用复制构造函数,依此类推...(你会因为“制作一份副本需要制作一份副本”而陷入无限递归)

它为什么不能通过指针传递给实例? - Barry Wark
24
那么它就不再是一个复制构造函数,而只是一个接受指针的普通旧构造函数。 - Dennis Zickefoose
8
当编译器尝试通过调用object o(other_object)来自行复制一个对象时,通常会实现一个复制构造函数。但是,这只有在object具有按值或按引用接受另一个object的构造函数时才起作用。您已经知道为什么按值传递不起作用了,所以唯一的方法是通过引用或常量引用进行传递。如果您的“复制构造函数”将采用指向object的指针,则编译器的代码必须是object o(&other_object)。因此,本质上您编写的构造函数满足编译器和用户的期望。 - wilhelmtell
是的,那很有道理。谢谢。 - Barry Wark
另一个由我的编译器指出的好理由是,如果您有一个带有纯虚函数的基类,则无法通过值初始化此变量。 - user451498

74

因为传值会调用复制构造函数 :)


27

除了传递引用之外的另一种方式是传递值。实际上,传递值是传递副本。需要使用拷贝构造函数来创建一个副本。

如果您必须在调用拷贝构造函数之前创建副本,这将是一个难题。

(我认为编译器会发生无限递归,因此您实际上永远不会得到这样的程序。)

除了理性原因外,在 §12.8/3 中标准中禁止这样做:

   

如果类X的第一个参数是类型(可以选择进行cv限定),并且没有其他参数,或者所有其他参数都具有默认参数,则类X的构造函数声明未定义。


编译器可以欣然输出无限递归;我怀疑这不是一个特例。然而,如果您声明一个带非引用参数的复制构造函数,则程序是不合法的。因此,您是正确的,它不应该编译。 - Dennis Zickefoose
@Dennis:我的意思是,如果你尝试编译这样一个程序,编译器会卡在尝试生成代码的过程中。它不会生成递归函数,因为难题发生在调用者中,在函数调用之前。 - Potatoswatter
无论你是否尝试使用它,该程序都是不正确的。仅仅定义构造函数就足以让编译器对你大喊大叫。 - Dennis Zickefoose
@Dennis:确实,尽管那只是一个规则。 - Potatoswatter

14

如果按值传递它,那么这将是无限递归的


10
无论何时调用一个函数(例如:int f(car c)),如果它的参数不是内置数据类型(这里是car),那么将由调用方提供的实际对象复制到被调用函数参数的变量中。
例如: car carobj; f(carobj); 即将carobj复制到c
需要将carobj复制到函数f中的参数c。为了实现复制,会调用复制构造函数。
在这种情况下,使用传值调用函数f,换句话说,函数f声明为按值传递。
如果函数f采用引用传递,则其声明如下: int f(car &c); 在这种情况下,carobj作为参数调用函数f就不需要复制构造函数。
在这种情况下,c成为carobj的别名。
根据以上两种情况,我总结如下:
  1. 如果函数声明以对象的值为参数,则调用该对象的复制构造函数。
  2. 如果函数声明以“按引用传递”的参数,则该参数成为调用者提供的对象的别名。无需复制构造函数!
现在问题是为什么需要按引用传递。如果复制构造函数接受引用,则接收变量成为提供的对象的别名。因此,在这种情况下不需要复制构造函数(即对自身的调用)将调用方提供的对象中的值复制到复制构造函数参数列表中的变量中。否则,如果复制构造函数将调用者提供的对象作为值进行传递,则需要给定对象的复制构造函数;因此,为了将来自调用者的提供对象传递到我们的函数本身(在这种情况下是复制构造函数),我们需要调用复制构造函数,即在函数声明期间调用同一函数。

这就是传递引用给复制构造函数的原因。


4
虽然你说的没错,但已经有四个回答解释了这个问题,而且解释得更清楚。我不明白为什么你觉得第五个答案会有所帮助。 - Ben Voigt

7

在IT技术中,传递对象时需要以引用方式而不是值的方式传递,因为如果您以值的方式传递,它的副本将使用复制构造函数进行构造。这意味着复制构造函数会调用自身来进行复制。这个过程将一直进行下去,直到编译器耗尽内存。


1
如果不是通过引用传递,则会按值传递。如果参数按值传递,则其复制构造函数将调用自身以将实际参数复制到形式参数。这个过程会一直进行,直到系统内存耗尽。 因此,我们应该通过引用传递,这样就不会调用复制构造函数。

1
非常重要的是将对象作为引用传递。如果将一个对象作为值传递给复制构造函数,那么它的复制构造函数会调用自身,将实际参数复制到形式参数。 因此,将启动无限的复制构造函数调用链。这个过程会一直进行,直到系统耗尽内存。
因此,在复制构造函数中,参数应始终以引用方式传递。

0
一个复制构造函数定义了什么是复制,所以如果我们只传递对象(我们将传递该对象的副本),但是为了创建副本,我们需要一个复制构造函数,因此它会导致无限递归。
因此,复制构造函数必须有一个引用作为参数。

0

所有答案都是正确的,但我想知道是否有任何东西能够对像我这样通过示例学习的初学者有所帮助。因此,这里是一个例子。

以这段代码为例:

#include<iostream>
#include<cstring>
using namespace std;

class String
{
private:
    char *s;
    int size;
public:
    String(const char *str)
    {

        size=strlen(str);
        s=new char[size+1];
        strcpy(s,str);
    }
    ~String()
    {
        delete[] s;
    }

    String(const String& old_str)
    {
        size=old_str.size;
        s=new char[size+1];
        strcpy(s,old_str.s);

    }
    void print()
    {
        cout<<s<<endl;
    }
    void change(const char *str)
    {
        delete [] s;
        size=strlen(str);
        s=new char[size+1];
        strcpy(s,str);
    }
};
int main()
{
    String str1("Hello World");
    String str2=str1;

    str1.print();
    str2.print();

    str2.change("Namaste");
    str1.print();
    str2.print();
    return 0;
}

这是拷贝构造函数:

String(const String& old_str)
        {
            size=old_str.size;
            s=new char[size+1];
            strcpy(s,old_str.s);
    
        }

假设我们不通过引用传递参数。即:

String(const String old_str)
        {
            size=old_str.size;
            s=new char[size+1];
            strcpy(s,old_str.s);
    
        }

看一下 int main() 函数。

String str2=str1

再次查看拷贝构造函数(不使用引用)。

String(const String old_str)

这意味着:

String old_str=str1

这基本上是在main()函数中写的相同的东西。因此,它再次调用了复制构造函数。在C++中调用复制构造函数的规则是当你有这样的情况:

ABC a;
ABC b=a;

希望这有帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接