通过隐式转换返回时,是否需要拷贝构造函数?

22

下面的代码在Visual C++ 2013中可以编译通过,但是在GCC或Clang下不能。

哪个是正确的?
当通过隐式转换返回对象时,是否需要一个可访问的复制构造函数?

class Noncopyable
{
    Noncopyable(Noncopyable const &);
public:
    Noncopyable(int = 0) { }
};

Noncopyable foo() { return 0; }

int main()
{
    foo();
    return 0;
}

GNU编译器集合(GCC):

error: 'Noncopyable::Noncopyable(const Noncopyable&)' is private
  Noncopyable(Noncopyable const &);
  ^
error: within this context
 Noncopyable foo() { return 0; }

Clang:


clang是一种流行的开源C++编译器,也支持其他语言。
error: calling a private constructor of class 'Noncopyable'
Noncopyable foo() { return 0; }
                    ^
note: implicitly declared private here
        Noncopyable(Noncopyable const &);
        ^
warning: C++98 requires an accessible copy constructor for class 'Noncopyable' when binding a reference to a temporary; was private [-Wbind-to-temporary-copy]
Noncopyable foo() { return 0; }
                           ^
note: implicitly declared private here
        Noncopyable(Noncopyable const &);
        ^

5
不错的问题,然而默认情况下,我选择gcc/clang作为C++的真正实现,而不是MSVC。 - masoud
@MM:谢谢 :) 是的,通常情况下是这样的。但在这种情况下,我觉得说不需要复制构造函数是有道理的,这也是我问的主要原因。即使没有复制省略,也没有任何需要复制的明显对象。 - user541686
可能是Copy Constructor Needed with temp object的重复问题。 - DarioP
@DarioP 看起来这是一个不同的问题。 - Luchian Grigore
3个回答

15

当你 return 一个表达式时,会创建一个临时对象,该对象的类型与表达式的返回类型相同,并用该表达式初始化。然后将该对象移动(如果无法移动,则复制)到返回值中。因此,您需要一个可访问的复制或移动构造函数。

但是,可以通过使用大括号列表直接初始化返回值。所以以下代码可以工作:

Noncopyable foo() { return {0}; }

类似的案例在实例中


+1 这真的很酷,但我不明白:这是否也需要从 initializer_list 进行转换? - user541686
1
@Mehrdad 不是的。像这样使用大括号不会产生initializer_list,而是产生一个braced-init-list(语法中的非终端符号)。粗略地说,这是“原地初始化”的语法。只有当类有一个initializer_list构造函数时,才会被解释为“创建一个initializer_list并用它进行初始化”。 - Angew is no longer proud of SO
有趣,我没有意识到。谢谢! - user541686
很好的回答,但你有为什么这个有效的解释或者标准中的引用吗? - juanchopanza
3
@juanchopanza 喜欢 [stmt.return]/2 吗?“带有花括号初始化列表的返回语句通过从指定的初始化器列表进行复制列表初始化(8.5.4)来初始化要从函数返回的对象或引用。” - dyp

5

12.8 复制和移动类对象 [class.copy]

1/ 类对象可以通过初始化(12.1、8.5),包括函数参数传递(5.2.2)和函数返回值(6.6.3),以两种方式进行复制或移动[...]

6.6.3 return语句 [stmt.return]中:

2/ [...] 表达式的值被隐式转换为它所在函数的返回类型。返回语句可以涉及临时对象的构造和复制或移动(12.2)[...]

以及12.2 临时对象 [class.temporary]:

1/ 在各种上下文中创建类类型的临时对象:将引用绑定到prvalue (8.5.3),返回prvalue (6.6.3),创建prvalue的转换 (4.1、5.2.9、5.2.11、5.4)等。请注意: 即使没有调用析构函数或复制/移动构造函数,所有语义限制,如可访问性(Clause 11)和函数是否已删除(8.4.3),都必须得到满足[...]

我认为GCC和clang是正确的 - 我甚至可以说,每次按值返回时,返回类型都必须具有可访问的复制或移动构造函数。

逻辑是创建一个临时对象将原始类型转换为新类型(从intNoncopyable),然后复制该临时对象并返回函数。

这本质上与以下代码相同:

Noncopyable foo() { return Noncopyable(0); }

你会期望那里需要一份拷贝吗?我肯定会。

4
  • foo函数通过值返回一个Noncopyable对象。因此,理论上必须调用复制构造函数。

  • 如果您提供了复制构造函数(即public)并打印一条消息来标志其调用,则会发现该消息未被打印DEMO,只有重载的转换运算符被调用。

  • 这是由于复制省略优化的原因。

  • 因此,并不是重载的转换运算符需要复制构造函数,而是foo的返回语句需要复制构造函数,因为你是通过值返回的。

  • 最终,复制构造函数由于复制省略而不会被调用,但仍然必须可用。


因为你是唯一一个提到复制省略/RVO的人,所以给你点赞。 - underscore_d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接