为啥 C++ 复制构造函数必须使用 const 对象?

Posted

技术标签:

【中文标题】为啥 C++ 复制构造函数必须使用 const 对象?【英文标题】:Why C++ copy constructor must use const object?为什么 C++ 复制构造函数必须使用 const 对象? 【发布时间】:2013-06-02 03:25:42 【问题描述】:

我知道当我们定义一个类时,类的复制构造函数是必要的,因为Rule of three 状态。我还注意到复制构造函数的参数通常是const,如下代码所示:

class ABC 
public:
    int a;
    int b;
    ABC(const ABC &other)
     
        a = other.a;
        b = other.b;
    

我的问题是如果复制构造函数的参数不是 const 会发生什么:

class ABC

    public:
    int a;
    int b;
    ABC(ABC &other)
     
        a = other.a;
        b = other.b;
    

我知道在某些情况下,如果复制构造函数的参数是 const,那么第二个实现会失败。此外,如果复制构造函数的参数是 const,则要复制的对象在此过程中不会更改其内容。但是,我确实注意到有些人仍然使用第二个实现而不是第一个。是否有任何理由首选第二种实现?

【问题讨论】:

为什么A要在ABC B(A)中修改?这没有什么意义,而且是非常不直观的行为。 可能是因为作者忘了把它设为const。 也可能是有些人试图像auto_ptr那样耍花招。当然,考虑到即使是标准委员会也无法做到这一点,这样做是一个非常的坏主意。 我可以想象复制构造函数需要使用 &other 的方法,这些方法本身没有声明为const。可能并希望以一种不会改变 &other 的方式。 @flaschenpost:那么这些方法应该声明为const 【参考方案1】:

从逻辑上讲,修改您只想复制的对象应该是没有意义的,尽管有时它可能有一些意义,例如您想存储此次数的情况对象已被复制。但这可以与存储此信息的mutable 成员变量一起使用,并且甚至可以针对 const 对象进行修改(第二点将证明这种方法的合理性)

您希望能够创建 const 对象的副本。但是,如果您没有使用 const 限定符传递参数,则无法创建 const 对象的副本...

您无法从临时引用创建副本,因为临时对象是右值,不能绑定到非 const 引用。更详细的解释,我建议Herb Sutter's article on the matter

【讨论】:

还有一个事实是,采用非常量引用的版本不适用于临时版本。 请注意:即使是引用计数智能指针也不需要可变变量。引用计数器不位于智能指针本身,而是通常位于特殊的中间对象或引用对象内部(侵入式实现)。所以原始 smartpointer 保持不变,可以声明为 const。 @user396672 我会更正以删除误导性示例【参考方案2】:

你的类的任何消费者最不希望看到的就是一个复制构造函数,它改变了被复制的对象!因此,您应该始终标记为 const。

【讨论】:

【参考方案3】:

这里可能需要 const 的原因有两个:

    它可确保您在制作副本时不会意外“损坏”原件 - 这是一件好事,因为您真的不希望在制作副本时更改您的原始对象! 您可以传入除基本对象以外的其他内容 - 因为构造函数需要引用,如果它本身不是对象 - 例如表达式。

以第二种情况为例:

 class ABC
    
       public:
           int a;
           int b;
       ABC(const ABC &other)
        
         a = other.a;
         b = other.b;
       
       ABC operator+(const ABC &other)
       
           ABC res;
           res.a = a + other.a;
           res.b = b + other.b;
           return res;
       
    

  ...
  ABC A;
  a.a = 1;
  a.b = 2;
  ABC B(a+a);

如果构造函数是ABC(ABC &other),则不会编译,因为a+a 是ABC 类型的临时对象。但是如果是ABC(const ABC &other),我们可以使用计算的临时结果,仍然作为参考传入。

【讨论】:

【参考方案4】:

正如其他几个答案所指出的,修改其参数的复制构造函数将是一个令人不快的惊喜。然而,这不是唯一的问题。复制构造函数有时与 临时变量 的参数一起使用。 (示例:从函数返回。)并且对临时对象的非 const 引用不会飞,正如 SO 上的 elsewhere 所解释的那样。

【讨论】:

【参考方案5】:

如果复制构造函数未将其参数指定为 const,则此片段将无法编译。

const ABC foo;
ABC bar(foo);

【讨论】:

或者,也许更狡猾的是,这也不会:ABC bar(some_func_returning_ABC());【参考方案6】:

复制构造函数不应该修改它正在复制的对象,这就是为什么constother 参数上是首选的原因。两者都可以,但首选 const,因为它明确指出传入的对象不应被函数修改。

const 仅供用户使用。实际的可执行文件不存在。

【讨论】:

不完全正确;如果对象确实,例如引用计数,则可能需要修改 rhs。 @OliCharlesworth:我认为应该是mutable 成员,因为通常引用计数不是可观察状态的一部分 @AndyProwl:同意。我只是想消除 rhs 从未被修改过的断言;) @OliCharlesworth:如果对象使用引用计数,那么引用计数不能是源对象的一部分(即直接或间接子对象),因此仍然不应该修改 rhs(尽管它“部分拥有”或指向的东西可以修改)并且仍然应该是常量。 @AndyProwl:绝对不是!如果引用计数是可变的,它仍然是源对象的子对象,并且会在源对象的生命周期结束时被销毁。这对于现在(大概)具有指向不再存在的引用计数的指针的副本是不利的。【参考方案7】:

除了复制构造函数不应该修改源实例的基本假设之外,本文详细阐述了使用 const 的实际技术原因:

http://www.geeksforgeeks.org/copy-constructor-argument-const/

即我引用:

"...编译器创建的临时对象不能绑定到非常量 参考...”

【讨论】:

【参考方案8】:

这不是技术意义上的“必须”。我们甚至在标准中有这样的野兽,尽管it got deprecated。按照链接进行推理。

我们期望复制的语义是保持“模板”不变,并提供一个在所有方面都完全相同的克隆,你很难分辨出原始的形式。

您应该三思而后行才能拥有一个不这样做的复制ctor。它会让用户感到惊讶,并可能引入错误。以及沮丧和噪音,只需尝试在 Google 上搜索“auto_ptr 向量”即可查看计数。

问题的其余部分可能是“我发誓在实施中不会触碰原件,但想要不同的签名”。那要什么签名呢?让我们试试 T 和 T&。

T 退出,因为它需要复制 ctor 才能使用,而我们正在实现这一点。递归:参见递归

剩下的就是 T&。这实际上适用于很多情况。但是,如果您的原始对象碰巧以 const 形式存在,或者是临时的,那就失败了。为什么要阻止完全不下雨的合理案例?

【讨论】:

【参考方案9】:

复制构造函数需要const 引用以避免无限循环。

class Cat

    //code
    Cat(Cat c)
    
        //code
    
    //code
;

当复制构造函数被调用时,它将创建对象c,即它会执行类似Cat c = sourceObj的东西。该语句应该由 复制赋值运算符 计算,但事实并非如此。这是因为c 还不存在,它要被制造。所以任务将交给复制构造函数。

无限循环!!

我很惊讶没有人提到这个明显的原因。

其他原因:

你很少(实际上从不)改变sourceObj 非常量绑定到临时对象。

【讨论】:

Infinite loop!! 解释参考,但不是const,@infinite-loop 的回答似乎更贴切

以上是关于为啥 C++ 复制构造函数必须使用 const 对象?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在通过 const 引用传递临时值时调用复制构造函数?

c++ 拷贝构造函数与赋值运算符重载函数的区别是

为啥复制构造函数应该在 C++ 中通过引用来接受它的参数?

c++中重载输出操作符,为啥要返回引用

c++中拷贝构造函数和赋值运算符重载本质上一样么

稍微深入点理解C++复制控制转