【问题标题】:Copy assignment for unimplemented copy constructor未实现的复制构造函数的复制赋值
【发布时间】:2012-01-08 16:37:19
【问题描述】:
#include "stdafx.h"

class Base
{
public:
    Base(){}
    virtual ~Base(){}
private:
    Base(const Base &other) ;   // Only declaration! No definition.
    Base &operator=(const Base &other);
} ;

int _tmain(int argc, _TCHAR* argv[])
{
    const Base b ;          // ok
    const Base *pb = &Base() ;      // ok
    const Base &qb = Base() ;       // Illegal, why?

    return 0;
}

然后,看下面的代码:

#include "stdafx.h"

class Base
{
public:
    Base(){}
    virtual ~Base(){}
public:
    Base(const Base &other) ;           // Only declaration! No definition.
    Base &operator=(const Base &other);
} ;

int _tmain(int argc, _TCHAR* argv[])
{
    const Base b ;          // ok
    const Base *pb = &Base() ;      // ok
    const Base &qb = Base() ;       // It's ok! why?

    return 0;
}

【问题讨论】:

  • 您应该添加更多文字来解释您要查找的内容。
  • 请注意,const Base *pb = &Base() ok,应该从编译器发出警告
  • 这个const Base *pb = &Base() ; 绝对 OK。这是一个微软扩展。
  • const Base &bp = Base(); 涉及对临时对象的引用的生命周期延长(第 12.2 节临时对象)
  • @ybungalobill 这不是 Microsoft 扩展。 GCC 和标准也允许它。但是,它仍然是使程序崩溃的好方法。

标签: c++ constants variable-assignment language-lawyer copy-constructor


【解决方案1】:

忽略采用临时地址的const *Base = &Base();,在大多数情况下会导致未定义的行为,因为在表达式的末尾,临时将被销毁,并且指针的任何取消引用都是 UB

当您尝试将常量引用绑定到临时变量时,该语言声明该操作(语义上)是将临时变量复制到未命名变量然后将引用绑定到该变量的操作。

const type& r = f();    // where f() returns a type (not a reference)

相当于:

const type __tmp = f(); // __tmp variable created by the compiler
const type& r = __tmp;

这解释了为什么在第一种情况下,因为无法访问复制构造函数,所以不允许编译器创建__tmp 变量(const type __tmp = f() 无法编译)并告诉你。

现在标准允许编译器删除变量的副本,特别是在该行中,允许编译器将__tmpf() 的结果放在内存中完全相同的位置1 并避免执行复制。在您的第二种情况下,编译器已检查是否允许复制(复制构造函数可用),但已优化复制,因此它不会调用该函数。

为什么它认为构造函数是可用的,即使它没有被定义?好吧,这是单独编译模型的一部分,编译器在决定构造函数是否有效时,只检查当前的翻译单元,它不知道复制构造函数是否可以在不同的 TU 中使用.因为省略了复制,所以二进制文件中没有对复制构造函数的调用,并且链接器不需要解析该符号,因此它可以正常编译和链接。


1有关更多详细信息,并且在标准范围之外,大多数编译器(据我所知)实现按大对象(不适合寄存器的对象)的值返回通过将隐藏指针传递给函数,因此调用者在本地堆栈中为__tmp 保留空间并将该指针传递给f()。使用这种调用约定,在这种情况下,返回的对象根本不存在,即使调用不用于初始化新对象也是如此。

在示例中,返回的类型适合寄存器,许多编译器会在返回之前将结果值存储在寄存器中。这是必须完成复制(概念上)的确切情况,因为您无法绑定对寄存器的引用,但对复制构造函数的调用再次被省略并且编译器只是将 register 存储在 __tmp 的位置。

【讨论】:

  • const *Base = &Base() 也被 GCC 所允许(尽管这显然是无稽之谈)。
  • @Crashworks 你说得对,我已经习惯了在程序中出现 error,我希望编译器不会接受它。该代码不正确,但可能在语义上无效(您永远不能取消引用指针,因为那将是 UB)。无论如何,GCC 确实会警告您做错了什么。感谢您指出错误。
【解决方案2】:

compiler's reported errors给你答案:

const Base &qb = Base() ;

调用 CBase 的复制构造函数,在上面的示例中它是私有的,因此不可访问。

还有,这个:

const Base *pb = &Base();

是未定义的行为,将导致崩溃,因为pb 指向一个临时对象。更详细地说,这行代码的作用是:

  1. 在堆栈上为临时 Base 对象创建空间,并在其上调用 Base() 构造函数
  2. 分配pb 指向临时对象的地址。
  3. Destroy the temporary object, because it hasn't been assigned to anything。 (您只是将它的 地址 分配给一个指针,这不会影响它的生命周期。)
  4. pb 现在指向垃圾。

【讨论】:

  • 您可能有兴趣查找@sehe 的答案和那里的cmets。 (不,我不是说你错了,我只是怀疑你有多正确)。
  • const Base &qb = Base(); 应该是引用绑定,你能解释一下为什么要调用复制构造函数吗?
  • @fefe David Rodriguez 有更详细的答案。
  • @fefe:简短的回答:因为语言是这样说的。所谓的临时生命周期延长实际上是编译器在程序中创建一个新的未命名变量,通过从临时变量复制来初始化,然后将引用绑定到该变量。我手头没有生成适当报价的标准。
  • @DavidRodríguez-dribeas 非常感谢。我明白了。
猜你喜欢
  • 2011-06-09
  • 1970-01-01
  • 2012-02-12
  • 1970-01-01
  • 2012-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多