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

Why C++ copy constructor must use const object?

提问人:feelfree 提问时间:6/6/2013 最后编辑:Saurav Sahufeelfree 更新时间:11/30/2020 访问量:32270

问:

我知道当我们定义类时,类的复制构造函数是必要的,因为三态法则。我还注意到复制构造函数的参数通常如以下代码所示: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,则要复制的对象在此过程中不会更改其内容。但是,我确实注意到有些人仍然使用第二种实现而不是第一种实现。是否有任何理由首选第二种实现?

C++ 复制构造函数

评论

4赞 juanchopanza 6/6/2013
为什么要修改 ?这没什么意义,而且是非常不直观的行为。AABC B(A)
10赞 Oliver Charlesworth 6/6/2013
可能是因为作者忘了让它变得常量。
3赞 celtschk 6/6/2013
也可能是有些人试图耍花招。当然,鉴于即使是标准委员会也无法做到这一点,这样做是一个非常糟糕的主意。auto_ptr
1赞 flaschenpost 6/6/2013
我可以想象复制构造函数需要使用 &other 的方法,这些方法没有声明为它们自己。可能并希望以一种不会改变的方式和其他方式。const
1赞 celtschk 6/6/2013
@flaschenpost:那么这些方法应该被声明。const

答:

1赞 Serdalis 6/6/2013 #1

复制构造函数不应修改它从中复制的对象,这就是参数首选的原因。两者都有效,但首选,因为它明确指出传入的对象不应由函数修改。constotherconst

const仅供用户使用。对于实际可执行文件,它不存在。

评论

2赞 Oliver Charlesworth 6/6/2013
不完全正确;如果对象执行参考计数,则可能需要修改 RHS。
4赞 Andy Prowl 6/6/2013
@OliCharlesworth:我认为它应该是一个成员,因为通常引用计数不是可观察状态的一部分mutable
0赞 Oliver Charlesworth 6/6/2013
@AndyProwl:同意。我只是想消除 rhs 从未被修改过的断言;)
1赞 CB Bailey 6/6/2013
@OliCharlesworth:如果对象使用引用计数,则引用计数不能是源对象的一部分(即直接或间接子对象),因此 rhs 仍然不应该被修改(尽管它“部分拥有”或指向的东西可以被修改)并且仍然应该是常量。
0赞 CB Bailey 6/6/2013
@AndyProwl:绝对不是!如果引用计数是可变的,它仍然是源对象的子对象,并且会在源对象的生存期结束时被销毁。这对副本不利,因为副本现在(大概)有一个指向不再存在的引用计数的指针。
14赞 Bathsheba 6/6/2013 #2

任何类的使用者最不希望看到的是更改所复制对象的复制构造函数!因此,应始终标记为 const。

3赞 Paul Mitchell 6/6/2013 #3

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

const ABC foo;
ABC bar(foo);

评论

3赞 Angew is no longer proud of SO 6/6/2013
或者,也许更狡猾的是,这也不会:ABC bar(some_func_returning_ABC());
41赞 JBL 6/6/2013 #4
  • 从逻辑上讲,修改您只想复制的对象应该是没有意义的,尽管有时它可能有某种意义,例如您想要存储该对象被复制的时间数的情况。但这可以与存储此信息的成员变量一起使用,甚至可以对 const 对象进行修改(第二点将证明这种方法的合理性)mutable

  • 您希望能够创建 const 对象的副本。但是,如果你没有用 const 限定符传递你的参数,那么你就不能创建 const 对象的副本......

  • 您无法从临时引用创建副本,因为临时对象是右值,并且不能绑定到对非 const 的引用。如需更详细的解释,我建议阅读 Herb Sutter 关于此事的文章

评论

7赞 juanchopanza 6/6/2013
还有一个事实是,采用非常量引用的版本不适用于临时版本。
0赞 user396672 6/6/2013
请注意:即使是引用计数智能指针也不需要可变对象。引用计数器不位于智能指针本身中,而是通常位于特殊的中间对象中或引用对象内部(侵入式实现)。因此,原始 smartpointer 保持不变,可以声明为 const。
0赞 JBL 6/6/2013
@user396672 然后我会纠正以删除误导性示例
12赞 Mats Petersson 6/6/2013 #5

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

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

举例说明第二种情况:

 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(ABC &other)a+aABC(const ABC &other)

6赞 Andrew Lazarus 6/6/2013 #6

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

0赞 Balog Pal 6/6/2013 #7

从技术意义上讲,这不是“必须”。我们甚至在标准中也有这样的野兽,尽管它已被弃用。点击链接进行推理。

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

这是意料之中的,您应该三思而后行,以拥有一个不这样做的副本。它会让用户感到惊讶,并可能引入错误。还有沮丧和噪音,试着在谷歌上搜索“auto_ptr载体”只是为了看看计数。

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

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

这就剩下 T&.这实际上适用于很多案件。但是,如果你的原始对象碰巧以常量形式存在,或者是临时的,那就失败了。为什么要阻止这个不下雨的明智案例呢?

1赞 infinite-loop 11/5/2017 #8

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

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

也就是说,我引用:

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