8

我正在阅读直接初始化和复制初始化之间的区别(第 8.5/12 节):

T x(a);  //direct-initialization
T y = a; //copy-initialization

我从阅读有关复制初始化的内容中了解到,它需要可访问且非显式的复制构造函数,否则程序将无法编译。我通过编写以下代码来验证它:

struct A
{
   int i;
       A(int i) : i(i) { std::cout << " A(int i)" << std::endl; }
   private:
       A(const A &a)  {  std::cout << " A(const A &)" << std::endl; }
};

int main() {
        A a = 10; //error - copy-ctor is private!
}

GCC 给出一个错误(ideone)说:

prog.cpp:8: 错误: 'A::A(const A&)' 是私有的

到目前为止一切都很好,重申 Herb Sutter 所说的话,

复制初始化是指使用复制构造函数对对象进行初始化,必要时先调用用户定义的转换,等价于“T t = u;”的形式:


之后,我通过注释private关键字使 copy-ctor 可访问。现在,我自然希望打印以下内容:

A(常量 A&)

但令我惊讶的是,它改为打印(ideone):

A(int i)

为什么?

好吧,我知道首先A创建10一个类型的临时对象int,通过使用A(int i),在此处根据需要应用转换规则(第 8.5/14 节),然后它应该调用 copy-ctor 来初始化a。但它没有。为什么?

如果允许实现消除调用复制构造函数的需要(第 8.5/14 节),那么为什么在声明复制构造函数时它不接受代码private?毕竟,它没有调用它。这就像一个被宠坏的孩子,他先恼火地要一个特定的玩具,当你给他一个,那个特定的玩具,他就把它扔在你背后。:|

这种行为会不会很危险?我的意思是,我可能会在 copy-ctor 中做一些其他有用的事情,但如果它不调用它,那么它不会改变程序的行为吗?

4

6 回答 6

10

您是在问为什么编译器会进行访问检查?C++03 中的 12.8/14:

如果隐式使用对象的复制构造函数或复制赋值运算符并且特殊成员函数不可访问,则程序是非良构的

当实现“省略复制构造”(12.8/15 允许)时,我不认为这意味着复制 ctor 不再“隐式使用”,它只是没有被执行。

或者你在问为什么标准这么说?如果复制省略是此规则关于访问检查的一个例外,那么您的程序在成功执行省略的实现中将是格式良好的,但在不执行省略的实现中格式是错误的。

我很确定作者会认为这是一件坏事。当然,以这种方式编写可移植代码更容易——编译器会告诉您是否编写了尝试复制不可复制对象的代码,即使在您的实现中碰巧省略了副本。我怀疑在检查访问之前确定优化是否成功(或将访问检查推迟到尝试优化之后)也会给实施者带来不便,尽管我不知道这是否值得考虑。

这种行为会不会很危险?我的意思是,我可能会在 copy-ctor 中做一些其他有用的事情,但如果它不调用它,那么它不会改变程序的行为吗?

当然这可能是危险的 - 当且仅当对象被实际复制时,复制构造函数中的副作用才会发生,并且您应该相应地设计它们:标准说可以省略副本,所以不要将代码放在复制构造函数中,除非您很高兴它在 12.8/15 中定义的条件下被省略:

MyObject(const MyObject &other) {
    std::cout << "copy " << (void*)(&other) << " to " << (void*)this << "\n"; // OK
    std::cout << "object returned from function\n"; // dangerous: if the copy is
      // elided then an object will be returned but you won't see the message.
}
于 2011-05-28T17:51:14.430 回答
5

C++ 明确允许涉及复制构造函数的若干优化,这些优化实际上改变了程序的语义。(这与大多数不影响程序语义的优化相反)。特别是,在某些情况下,如果编译器知道现有对象将变得不可访问,则允许编译器重用现有对象,而不是复制一个。这(复制构造)就是这样一种情况;另一个类似的情况是“返回值优化”(RVO),如果你声明一个保存函数返回值的变量,那么 C++ 可以选择在调用者的框架上分配它,这样它就不需要在函数完成时将其复制回调用者。

通常,在 C++ 中,如果您定义了一个具有副作用的复制构造函数,或者执行除了复制之外的任何其他操作,那么您就是在玩火。

于 2011-05-28T18:02:05.083 回答
4

在任何编译器中,语法[和语义]分析过程都在代码优化过程之前完成。

代码必须在语法上有效,否则它甚至无法编译。只有在后期阶段(即代码优化),编译器才决定忽略它创建的临时变量。

所以你需要一个可访问的副本 c-tor。

于 2011-05-28T17:20:59.277 回答
1

在这里你可以找到这个(你的评论;)):

[标准] 还说可以省略临时副本,但仍然必须检查复制构造函数的语义约束(例如可访问性)。

于 2011-05-28T17:41:16.753 回答
0

RVO 和 NRVO,伙计。复制省略的完美案例。

于 2011-05-28T17:04:26.063 回答
0

这是编译器的优化。

在评估:A a = 10;而不是:

  1. 首先通过构造一个临时对象A(int)

  2. 通过复制构造a函数构造并传入临时;

编译器将简单地a使用A(int).

于 2011-05-28T17:09:42.367 回答