【问题标题】:Is a copy constructor required when returning by implicit conversion?通过隐式转换返回时是否需要复制构造函数?
【发布时间】:2014-08-05 23:43:36
【问题描述】:

以下代码在 Visual C++ 2013 中可以正常编译,但不能在 GCC 或 Clang 下编译。

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

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

Noncopyable foo() { return 0; }

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

海合会:

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

叮当声:

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 &);
        ^

【问题讨论】:

  • 很好的问题,但是默认情况下,我投票支持 gcc/clang 作为 C++ 的真正实现,而不是 MSVC。
  • @MM.: 谢谢 :) 是的,这通常是真的。在这种情况下,我觉得说不需要复制构造函数是有道理的,这主要是我问的原因。没有任何明显需要复制的对象(即使没有复制省略)。
  • @DarioP 这看起来像是一个不同的问题。

标签: c++ copy-constructor implicit-conversion copy-elision


【解决方案1】:

当您return 一个表达式时,会创建一个返回类型的临时对象,使用该表达式进行初始化,然后将其移动(或复制,如果不能选择移动)到返回值中。所以你需要一个可访问的副本或移动构造函数。

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

Noncopyable foo() { return {0}; }

live example 中的类似情况。

【讨论】:

  • +1 这真的很酷,但我不明白:这不是也需要从initializer_list 转换吗?
  • @Mehrdad 不。像这样使用的大括号不会产生initializer_list,它们会产生一个braced-init-list(语法的非终结符)。粗略地说,它是“就地初始化”的语法。只有如果该类有一个initializer_list 构造函数,这才被解释为“创建一个initializer_list 并用它初始化。”
  • 有趣,没有意识到。谢谢!
  • 不错的答案,但你有解释为什么会这样,或者引用标准吗?
  • @juanchopanza 喜欢 [stmt.return]/2 吗? “带有花括号初始化列表的返回语句通过指定初始化列表中的复制列表初始化(8.5.4)初始化要从函数返回的对象或引用。”
【解决方案2】:

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

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

6.6.3中的return语句[stmt.return]

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

12.2 临时对象[class.temporary]

1/ 类类型的临时对象是在各种上下文中创建的:将引用绑定到纯右值 (8.5.3),返回 prvalue (6.6.3),创建prvalue (4.1, 5.2.9, 5.2.11, 5.4), [...] 注意:即使没有调用析构函数或复制/move 构造函数,全部 语义限制,例如可访问性(第 11 条)和功能是否被删除(8.4.3),应 满意。 [...]

我认为 GCC 和 clang 是正确的 - 我什至会说任何时候按值返回,返回类型都必须具有可访问的副本或移动构造函数。

逻辑是创建一个临时对象以将原始类型转换为新类型(intNoncopyable),然后将该临时对象的副本返回给函数。

本质上是一样的:

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

您希望那里需要一份副本吗?我当然会。

【讨论】:

    【解决方案3】:
    • 函数foo 按值返回Noncopyable 对象。因此,理论上必须调用复制构造函数。

    • 1234563 .
    • 这是由于复制省略优化的事实。

    • 因此,不是重载的转换运算符需要拷贝构造函数,而是foo的return语句需要拷贝构造函数,因为你按值返回。

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

    【讨论】:

    • 赞成成为唯一真正提及复制省略/RVO 的人。
    猜你喜欢
    • 1970-01-01
    • 2011-09-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-31
    • 1970-01-01
    • 2019-05-14
    • 1970-01-01
    相关资源
    最近更新 更多