为啥 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】:
复制构造函数不应该修改它正在复制的对象,这就是为什么const
在other
参数上是首选的原因。两者都可以,但首选 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 对象?的主要内容,如果未能解决你的问题,请参考以下文章