【问题标题】:Explicit move constructor显式移动构造函数
【发布时间】:2014-03-19 08:09:25
【问题描述】:

尝试编译以下代码:

struct Foo
{
    explicit Foo ( void ) { }
    explicit Foo ( Foo&& rhs ) { }
};

Foo bar ( void )
{
    return Foo();
}

得到以下错误:

调用 'Foo' 的隐式删除的复制构造函数

嗯,很明显,copy-ctor 被隐式​​删除了。

问题1:为什么编译器需要Foo的copy-ctor?我希望 bar 的返回值是从右值 Foo() 和 move-ctor 构造的。

然后我将 move-ctor 重新声明为隐式,一切编译成功。

问题 2:为什么当我将 move-ctor 重新声明为隐式时,编译器不再需要 copy-ctor?

问题 3:explicit 关键字在复制和移动 ctor 的上下文中是什么意思,因为它绝对意味着与常规 ctor 的上下文不同的东西。

【问题讨论】:

  • @MarkGarcia 谢谢,但即使在修复之后,所有问题仍然是真实的。
  • 有趣。我从不认为explicit 会应用于左值/右值引用。
  • @MarkGarcia 仍然需要在Foo&&Foo 之间进行隐式转换。
  • 请注意,代码在(未标记的)C++17 中非常好。此外,bar 定义会很好,即使复制和移动 ctor 都被删除/无法访问/等。 C++17 强制要求 RVO 并为其应用程序使用不同的规则。

标签: c++ c++11 constructor move-constructor c++14


【解决方案1】:

这是因为返回值被视为implicit conversion

引用 C++11 标准:

6.6.3 返回语句

2 [...]

带有非 void 类型表达式的 return 语句只能用于返回值的函数;表达式的值返回给函数的调用者。 表达式的值被隐式转换为它出现的函数的返回类型。 return 语句可能涉及临时对象 (12.2) 的构造和复制或移动。 [...]

从返回表达式到保存返回值的临时对象的转换是隐式的。所以就像这会导致错误

Foo f = Foo();   // Copy initialization, which means implicit conversion

将代码作为您的示例也会触发类似的代码。


问题1:为什么编译器需要Foo的copy-ctor?我希望 bar 的返回值是从右值 Foo() 和 move-ctor 构造的。

因为上述原因,Foo(Foo&&) 不是可行的重载。规则规定,每当不能使用移动构造函数时,编译器就会考虑复制构造函数,在您的情况下,由于存在用户定义的移动构造函数,它会被隐式删除。

问题 2:为什么当我将 move-ctor 重新声明为隐式时,编译器不再需要 copy-ctor?

这是因为你的移动构造函数现在可以使用了。因此,编译器可以立即使用它,甚至无需考虑复制构造函数的存在。

问题 3:explicit 关键字在复制和移动 ctor 的上下文中是什么意思,因为它的含义肯定不同于常规 ctor 的上下文。

恕我直言,这没有意义,而且只会导致问题,就像您的情况一样。

【讨论】:

    【解决方案2】:

    bar 的返回类型为Foo。没有复制构造函数,并且显式移动构造函数无法工作,因为Foo&&Foo 之间的隐式转换仍然是必需的。从这个意义上说,explicit Foo(Foo&&) 与任何其他 explicit 转换构造函数没有什么不同。从不同类型的转换仍然会出现问题。这是一个使用int的类似示例:

    struct Foo
    {
        explicit Foo(int) {}
    };
    
    Foo bar ( void )
    {
        return 42; // error: could not convert '42' from 'int' to 'Foo'
    }
    

    Q1:因为没有其他东西可以使用。

    Q2:因为它使用移动构造函数将Foo&&隐式转换为Foo

    Q3:意思和普通的转换构造函数一样。

    【讨论】:

    • 什么是“移动复制”ctor?
    • @Angew Just Foo(Foo&&);
    • @Angew 我确定我在其他地方看到过这个术语。基本上,FooBar(Foo&&, Bar&&); 也是一个移动构造函数。但我意识到这可能会令人困惑,所以我会编辑。
    • 我不认为 FooBar(Foo&&, Bar&&) 是一个移动 ctor。至少C++11[class.copy]§3 声明“类X 的非模板构造函数是一个移动构造函数,如果它的第一个参数是X&&const X&&volatile X&&const volatile X&& 类型,或者有没有其他参数,否则所有其他参数都有默认参数。”无论如何,+1
    • @Angew 你是对的。我可能早在这一切正式化之前就已经掌握了这个术语。
    【解决方案3】:

    这与 C++ 中重载解析的工作方式有关。

    重载解析的第一步是形成一组候选函数。第二步是将候选函数集缩小为一组可行的函数。第三步是选择唯一的最佳可行函数,如果有的话。如果最好的可行函数被删除,那么程序是非良构的。

    因为移动构造函数声明为explicit,所以它不是候选函数,用于将Foo() 隐式转换为函数的返回类型。唯一的候选函数是Foo::Foo(const Foo&),隐式声明的复制构造函数。 这是一个候选函数,即使它被声明为已删除。它是通过重载决议选择的,是唯一可行的功能;然后你会因为尝试调用已删除的函数而出错。

    如果不声明移动构造函数explicit,那么移动构造函数和隐式声明的复制构造函数都是候选函数。在这种情况下,两者都将是可行的。但是,移动构造函数赢得了重载决议,因为右值更喜欢绑定到右值引用而不是 const 左值引用。因此移动构造函数是最可行的函数。在这种情况下,复制构造函数被删除并不重要,因为它会在重载决议中丢失。

    tl;博士:

    答案 1:因为移动构造函数不是转换的候选函数

    答案2:因为move构造函数是一个候选函数,并且赢得了重载决议。

    答案 3:不,意思是一样的。

    【讨论】:

      猜你喜欢
      • 2011-10-09
      • 2018-03-26
      • 2012-06-15
      • 2020-09-07
      • 1970-01-01
      • 2011-05-22
      • 1970-01-01
      • 2012-11-09
      • 1970-01-01
      相关资源
      最近更新 更多