在R值引用和代码重复上重载 [英] Overloading on R-value references and code duplication

查看:113
本文介绍了在R值引用和代码重复上重载的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

请考虑以下内容:

  struct vec 
{
int v [3]

vec():v(){};
vec(int x,int y,int z):v {x,y,z} {};
vec(const vec& that)= default;
vec& operator =(const vec& that)= default;
〜vec()= default;

vec& operator + =(const vec& that)
{
v [0] + = that.v [0];
v [1] + = that.v [1];
v [2] + = that.v [2];
return * this;
}
};

vec operator +(const vec& lhs,const vec& rhs)
{
return vec(lhs.v [0] + rhs.v [0],lhs.v [1] + rhs.v [1],lhs.v [2] + rhs.v [2]);
}
vec&&&运算符+(vec& lhs,const vec& rhs)
{
return move(lhs + = rhs);
}
vec&&&运算符+(const vec& lhs,vec&&&rhs)
{
return move(rhs + = lhs);
}
vec&&&运算符+(vec& lhs,vec&&&rhs)
{
return move(lhs + = rhs);
}

由于r值引用,这四个重载的operator +通过重用临时值创建的对象数量。但我不喜欢这里介绍的代码的重复。

解决方案

回收临时数据是一个有趣的想法,你不是唯一一个写的为此原因返回右值引用的函数。在旧的C ++ 0x草案运算符+(字符串&,字符串常量&)也被声明为返回右值引用。但这改变了好的理由。我看到这种重载和选择返回类型的三个问题。其中两个与实际类型无关,第三个参数指的是 vec 的类型。


  1. 安全问题。考虑这样的代码:

      vec a = .... 
    vec b = ....
    vec c = ....;
    auto&&& x = a + b + c;

    如果最后一个运算符返回右值引用, x 将是一个悬挂的引用。否则,它不会。这不是一个人为的例子。例如, auto&&& 特技在内部使用for范围循环,以避免不必要的副本。但是由于在引用绑定期间临时的生命期扩展规则不适用于只返回引用的函数调用的情况,你会得到一个悬挂引用。

      string source1(); 
    string source2();
    string source3();

    ....

    int main(){
    for(char x:source1()+ source2()+ source3()){}
    }

    如果最后一个运算符+返回了对第一个级联期间创建的临时变量的右值引用


  2. 在通用代码中,返回右值引用的函数强迫你写

    p>

      typename std :: decay< decltype(a + b + c)> :: type 

    而不是

      b + c)

    ,因为最后一个op +可能返回一个右值引用。


  3. 因为你的类型 vec 都是flat和小,这些op +重载几乎没有用。请参阅FredOverflow的答案。


结论:应避免使用带有右值引用返回类型的函数,短暂临时对象。 std :: move std :: forward 是此经验法则的特殊用途例外。

Consider the following:

struct vec
{
    int v[3];

    vec() : v() {};
    vec(int x, int y, int z) : v{x,y,z} {};
    vec(const vec& that) = default;
    vec& operator=(const vec& that) = default;
    ~vec() = default;

    vec& operator+=(const vec& that)
    {
        v[0] += that.v[0];
        v[1] += that.v[1];
        v[2] += that.v[2];
        return *this;
    }
};

vec operator+(const vec& lhs, const vec& rhs)
{
    return vec(lhs.v[0] + rhs.v[0], lhs.v[1] + rhs.v[1], lhs.v[2] + rhs.v[2]);
}
vec&& operator+(vec&& lhs, const vec& rhs)
{
    return move(lhs += rhs);
}
vec&& operator+(const vec& lhs, vec&& rhs)
{
    return move(rhs += lhs);
}
vec&& operator+(vec&& lhs, vec&& rhs)
{
    return move(lhs += rhs);
}

Thanks to r-value references, with these four overloads of operator+ I can minimize the number of objects created, by reusing temporaries. But I don't like the duplication of code this introduces. Can I achieve the same with less repetition?

解决方案

Recycling temporaries is an interesting idea and you're not the only one who wrote functions that return rvalue references for this reason. In an older C++0x draft operator+(string&&,string const&) was also declared to return an rvalue reference. But this changed for good reasons. I see three issues with this kind of overloading and choice of return types. Two of them are independent of the actual type and the third argument refers to the kind of type that vec is.

  1. Safety issues. Consider code like this:

    vec a = ....;
    vec b = ....;
    vec c = ....;
    auto&& x = a+b+c;
    

    If your last operator returns an rvalue reference, x will be a dangling reference. Otherwise, it won't. This is not an artificial example. For example, the auto&& trick is used in the for-range loop internally to avoid unnecessary copies. But since the life-time extension rule for temporaries during reference binding does not apply in case of a function call that simply returns a reference, you'll get a dangling reference.

    string source1();
    string source2();
    string source3();
    
    ....
    
    int main() {
      for ( char x : source1()+source2()+source3() ) {}
    }
    

    If the last operator+ returned an rvalue reference to the temporary that is created during the first concatenation, this code would invoke undefined behaviour because the string temporary would not exist long enough.

  2. In generic code, functions that return rvalue references force you to write

    typename std::decay<decltype(a+b+c)>::type
    

    instead of

    decltype(a+b+c)
    

    simply because the last op+ might return an rvalue reference. This is getting ugly, in my humble opinion.

  3. Since your type vec is both "flat" and small, these op+ overloads are hardly useful. See FredOverflow's answer.

Conclusion: Functions with an rvalue reference return type should be avoided especially if these references may refer to short-lived temporary objects. std::move and std::forward are special-purpose exceptions to this rule of thumb.

这篇关于在R值引用和代码重复上重载的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆