【问题标题】:Sharing implementations for const lvalue (const T&) and rvalue (T&&) overloads: just like what is done for const and non-const overloads共享 const 左值 (const T&) 和右值 (T&&) 重载的实现:就像对 const 和非 const 重载所做的那样
【发布时间】:2015-06-03 11:36:55
【问题描述】:

背景

以下代码块出现在 Scott Meyers 的名著"Effective C++" Item 3 中:

class TextBlock {
public:
    ...
    const char& operator[](std::size_t position) const
    {
        ...    // do bounds checking
        ...    // log access data
        ...    // verify data integrity
        return text[position];
    }
    char& operator[](std::size_t position)
    {
        ...    // do bounds checking
        ...    // log access data
        ...    // verify data integrity
        return text[position];
    }
    ...
private:
    std::string text;
};

作者指出,在上述实现中,const 和非const 重载的内容本质上是相同的。为了避免代码重复,可以这样简化:

class TextBlock {
public:
    ...
    const char& operator[](std::size_t position) const    // the same as before
    {
        ...
        ...
        ...
        return text[position];
    }
    char& operator[](std::size_t position)        // now just calls const op[]
    {
        return                                    // cast away const on
          const_cast<char&>(                      // op[]'s return type;
            static_cast<const TextBlock&>(*this)  // add const to *this's type;
              [position]                          // call const version of op[]
          );
    }
    ...
private:
    std::string text;
};

问题

我的问题是:

  • 我们什么时候需要一个重载const T&amp; 和另一个T&amp;&amp;? (这里,T 可能是模板参数或类类型,所以T&amp;&amp; 可能表示也可能不表示通用引用)我可以看到,在标准库中,许多类都提供了这两种重载。例如std::pairstd::tuple 的构造函数,有大量的重载。 (好的,我知道函数中,一个是拷贝构造函数,一个是移动构造函数。)

  • 是否有类似的技巧来共享 const T&amp;T&amp;&amp; 重载的实现?我的意思是,如果const T&amp;&amp; 重载返回一个复制构造的对象,而T&amp;&amp; 重载返回的是移动构造的东西,那么在共享实现之后,这个属性必须仍然成立。 (就像上面的技巧:const返回const和非const返回非const,实现共享前后都一样)

谢谢!

说明

我所指的两个重载应该如下所示:

Gadget f(Widget const& w);
Gadget f(Widget&& w);

与右值引用返回无关,即:

Widget&& g(/* ... */);

(顺便说一下,my previous post 已经解决了这个问题)

在上面的f() 中,如果Gadget 既可复制构造又可移动构造,则无法(除了阅读实现)来判断返回值是复制构造还是移动构造。与返回值优化(RVO)/命名返回值优化(NRVO)无关。 (见my previous post

参考文献

Effective C++

std::pair::pair

std::tuple::tuple

When is it a good time to return by rvalue references?

【问题讨论】:

    标签: c++ c++11 rvalue-reference rvalue pass-by-rvalue-reference


    【解决方案1】:

    • 我们什么时候需要一个 const T& 的重载和另一个 T&& 的重载?

    基本上,当移动可以提高性能时,还应该有一个移动构造函数。对于您原本需要昂贵副本的功能也是如此。

    在您的示例中,您返回对char 的引用,但是不建议同时设置一个返回右值引用的函数。相反,按值返回并依赖编译器应用 RVO 的能力(参见例如here

    •是否有类似的技巧来共享 const T& 和 T&& 重载的实现?

    我经常发现使用通用引用(我很懒)设置构造函数或函数很有用,例如

    struct MyClass
    {
        template<typename T /*, here possibly use SFINAE to allow only for certain types */>
        MyClass(T&& _t) : t(std::forward<T>(_t)) {}
    private:
        SomeType t;
    };
    

    编辑:关于您的更新:如果您的函数 f 中有一个昂贵的 Widget 副本,建议还提供一个采用 Widget&amp;&amp; 的重载。

    Gadget f(Widget const& w)
    {
        Widget temp = w;  //expensive copy
    }
    Gadget f(Widget&& w)
    {
        Widget temp = std::move(w);  //move
    }
    

    您可以使用这样的函数模板组合这两个函数

    template<typename WidgetType
           // possibly drop that SFINAE stuff
           // (as it is already checked in the first assignment)
           , typename std::enable_if<std::is_convertible<std::remove_reference_t<WidgetType>, Widget>::value> >
    Gadget(WidgetType&& w)
    {
        Widget temp = std::forward<WidgetType>(w);
        //or
        std::remove_reference_t<WidgetType> temp2 = std::forward<WidgetType>(w);
    }
    

    ...我没有说它更好;-)。


    编辑 2:另见 this thread,它更彻底地解决了您的问题。

    【讨论】:

    • 已更新。请参阅我上面的说明。谢谢!
    • 感谢您的回答!您提到了“性能提升”并在代码中声明了temp。您是否假设temp 将在f() 中进行修改?如果temp不打算修改,w可以直接用在f()中,temp就没有必要了,导致f(Widget&amp;&amp;)也没有必要了,也就是说单独定义f(Widget const&amp;)就足够了,对吧?因为左值引用和右值引用都是引用(它们不复制任何内容),并且f(Widget const&amp;)f(Widget&amp;&amp;) 都与f(h()) 兼容,其中h() 类似于Widget h()。我说的对吗?
    • @SiuChingPong-AsukaKenji-:我并不是要假设函数 f 上的任何内容,除了它基于输入的对象创建一个新对象——无论出于何种原因(构造函数是一个常见的例子)。然后,将临时对象Widget() 传递给f(Widget const&amp;) 会导致函数内部的副本(至少在理论上,我不知道现代编译器是否没有就地创建对象)。如果这个副本很贵,而同时移动很便宜,那么提供过载f(Widget &amp;&amp;) 是明智的……如果你像我一样迂腐:-)
    • 对不起,但我担心将 const 左值引用绑定到临时对象不会复制它。如果临时对象是复制构造的,那么当参数是临时/未命名对象时,f(Widget);f(Widget const&amp;) 的含义相同。那么,f(Widget const&amp;) 中的const 将毫无意义:如果我们正在修改复制的对象,为什么需要const?这是一篇关于此的帖子(42 票的答案解释了我的观点):stackoverflow.com/q/1565600/142239
    • @SiuChingPong-AsukaKenji-:我想我们是在互相交谈。不是对引用的绑定复制对象。我假设某处在函数中完成了复制/移动......无论出于何种原因。基本示例是构造函数:假设在类A 中拥有b 类型的B 对象。如果您现在通过A(B()) 调用A(B const&amp; _b) : b(_b) {} ,它将导致新创建的对象B() 的副本。您可以使用移动构造函数来避免这种情况。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-30
    • 1970-01-01
    • 1970-01-01
    • 2016-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多