【问题标题】:C++11: call by value, move semantics and inheritanceC++11:按值调用、移动语义和继承
【发布时间】:2013-05-19 07:48:59
【问题描述】:

假设我有一个类,我打算直接公开为一个可实例化的类 给程序员:

class Base
{
public:
    Base(std::string text) : m_text(std::move(text)) {}
private:
    std::string m_text;
};

到目前为止一切顺利。这里不需要右值构造函数。 现在,在未来的某个时候,我决定扩展 Base:

class Derived : public Base
{
public:
    Derived(const std::string &text) : Base(text) {}
};

这让我很烦恼:我不能在 Derived 中按值获取字符串,因为这就是 基地已经在做 - 我最终会得到 2 个副本和 1 个移动。此处的 const-reference 构造函数还会对右值执行不必要的复制。

问题:如何只复制+移动一次(就像 Base 中的简单构造函数一样)而不添加更多构造函数?

【问题讨论】:

  • 为什么不能说Derived(std::string x) : Base(std::move(x)) { }?还是只继承基础构造函数?
  • @KerrekSB 将继承的构造函数放在一个答案中,它值得一票。

标签: c++ inheritance c++11 move move-semantics


【解决方案1】:

您不能只复制和移动一次,除非您更改类的设计并将其构造函数转变为(可能受 SFINAE 约束)模板化转发构造函数 (Yakk's answer shows how)。

虽然这样做可以在提供右值时只执行一次移动而不执行复制,而在提供左值时执行一次复制而不执行移动,但在大多数情况下这是一种矫枉过正。

作为基于模板的转发构造函数的替代方案,您可以在基类和派生类中提供 两个 构造函数:一个用于右值引用,一个用于对 @987654322 的左值引用@。但同样,这在大多数情况下是不必要的复杂化(并且在参数数量增加时不能很好地扩展,因为所需的构造函数的数量会呈指数增长)。

移动std::string 与复制指针和整数一样快(此处忽略 SSO 优化),除非您有真正的证据表明这是阻碍您的应用程序满足其性能的瓶颈,否则您不应该为此烦恼要求(难以置信)。

因此,只需让您的Derived 构造函数无条件地按值获取其参数,并在将其传递给基类的构造函数时将其移动:

class Derived : public Base
{
public:
    Derived(std::string text) : Base(std::move(text)) { }
};

如果您希望(或接受)Derive 继承 所有 Base 的构造函数,另一种选择是利用 C++11 的继承构造函数,如下所示:

class Derived : public Base
{
public:
    using Base::Base;
//  ^^^^^^^^^^^^^^^^^
};

【讨论】:

  • 我正在运行 VS2012,所以 using 不是一个选项。 :-) 我认为当您定义自己的构造函数时编译器不会生成移动构造函数,因此上述移动 - 在这种情况下 - 只会调用提供的构造函数(这意味着 2 个副本)?
  • @user2414893:根据标准,即使您定义了常规构造函数,编译器也应该隐式生成移动构造函数。如果您定义复制构造函数或析构函数(等等,这是一种简化),它不应该生成一个。然而,不知何故,MS 并不热衷于遵循标准,并且它不会永远隐式生成移动构造函数。如果你不想要它,你必须提供它。这就是说,我看不出这如何适用于您问题中的示例。我以为你在谈论构建一个Derived,并移动一个string,而不是移动一个Derived..
  • @user2414893 我不知道 VS2012 是否算作 C++11。 ;)
  • 嘿,那是不必要的! :-D 回到主题:Derived a("A"); 构造std::string("A"),调用Derived::Derived(第一个副本)然后Base::Base(第二个副本)+ 移入Base::Base(2 个副本,1 个移动)[假设没有隐式移动构造函数]。
  • @user2414893:如果您在谈论我的示例,那只是一步(从临时 string 初始化 Derived::Derived 的参数时)加上一步(将该参数传递给 @ 987654340@) 加一移动(当移动到数据成员时)。所以 3 步,没有副本。
【解决方案2】:

Derived 更改为以下内容:

class Derived : public Base
{
public:
    using Base::Base;
};

现在Derived 继承了Base 的构造函数。

有关更多信息,请参阅Forwarding all constructors in C++0x

【讨论】:

    【解决方案3】:

    使用 SFINAE 构造函数完美转发:

    class Derived : public Base
    {
    public:
      template<
        typename T,
        typename=typename std::enable_if<
          std::is_constructible<std::string, T&&>::value
        >::type
      >
      Derived(T&& text) : Base(std::forward<T>(text)) {}
    };
    

    一个缺点(除了上述构造的疯狂之外)是这将参与重载决议到一个过度贪婪的程度:它与任何可以转换为std::string 的参数几乎完美匹配。因此,如果您有另一个构造函数采用char const*,如果您传入char const* 变量,它可能会被忽略,因为T&amp;&amp; 可能匹配得更好。

    如果你的 DerivedBase 有一个 operator std::string,那么你就有祸了...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-26
      • 1970-01-01
      • 2020-09-12
      • 2023-03-26
      • 2014-08-16
      相关资源
      最近更新 更多