复制未实现的复制构造函数的分配

时间:2011-11-29 10:15:50

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

#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;
}

2 个答案:

答案 0 :(得分:2)

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

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

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()的结果放在内存中的完全相同的位置< sup> 1 并避免执行复制。在第二种情况下,编译器已检查是否允许复制(复制构造函数可用),但已优化了复制,因此它不会调用该函数。

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


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

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

的位置

答案 1 :(得分:1)

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现在指向垃圾。