R值似乎为未命名的临时工提供不完全支持,或者我在这里遗漏了什么?

时间:2017-05-16 19:59:36

标签: c++ c++11 optimization move

R值似乎为未命名的临时工提供不完全支持,或者我在这里遗漏了什么?

C ++ 11为rvalues提供了极好的支持来实现移动语义,对于将昂贵的分配和复制周期转换为快速且廉价的移动在恒定时间内非常有用,类似于移动引用。但是C ++ 11在游戏中来得很晚,到那时我使用了下面列出的基于类的解决方案,为昂贵的无名临时问题提供了一个完全开发的解决方案。

只有当我最近尝试用"现代"替换我的解决方案时... C ++ 11移动构造函数,我发现rvalue管理并不涵盖基于类的解决方案所涵盖的重要案例。一个代表性的例子是表达式A + B.当A是未命名的临时(rvalue)时,A + = B的就地实现是合适的,当A不是未命名的临时(左值)时,A + B计算新的结果。但是C + 11右值支持似乎只能解决一个正确的参数rvalue,而不是左rvalue。

通过扩展,此限制会影响基类型上的所有其他运算符和函数,这些运算符和函数可以在适当时将* this作为右值处理。注意,当A是左值而B是右值时,A + B甚至可以被计算为B + = A.完整解决方案的好处通常可以更多地应用于这个rvalues而不是右边的参数rvalues。如果C ++ 11在这里只提供了一半解决方案,那么下面基于类的解决方案对于许多事情来说仍然是非常优越的。我在这里错过了什么吗?

因此,让我们从S值类派生一个未命名的临时T类,向S和T添加适当的构造函数,赋值,运算符和函数,然后用T代替S作为所有函数和运算符的返回类型返回S结果。有了这个,我们得到了与rvalues相同的移动语义,并且支持额外的函数和运算符,可以在未命名的临时值上更快地运行。

class S {                      // S is a sample base type to extend
protected:
    mutable char* p;           // mutable pointer to storage
    mutable int length;        // mutable current length
    mutable int size;          // mutable current size
public:
    ~S ( );                    // S destructor
    S (char* s);               // construct from data
    S (const S& s);            // from another S
    S (const T& s);            // construct from an unnamed temporary

    T& result ( ) { return (T&)*this; }  // cast *this into a T& (an equivalent to std::move (*this))
    S& take (S& s);                      // free *this, move s to *this, put s in empty/valid state

    S& operator= (const S& s);           // copy s to *this
    S& operator= (const T& s);           // assign from unnamed temporary using take ( )

    S& operator+= (const S& v);          // add v to *this in-place
    S& operator-= (const S& v);          // subtract v from *this in-place
    S& operator<<= (Integer shift);      // shift *this in-place
    S& operator>>= (Integer shift);

    T operator+ (const S& v);       // add v to *this and return a T
    T operator- (const S& v);       // subtract v from *this and return a T
    etc...
};

class T : public S {               // T is an unnamed temporary S
private:
    T& operator= (const T& s);          // no public assignments
    void* operator new (size_t size);   // don't define -- no heap allocation
    void operator delete (void* ptr);
public:
    T (char* s) : S (s) { };            // create a new temporary from data
    T (const S& s) : S (s) { };         // copy a new temporary from a non-temporary
    T (const T& s) : S (s) { };         // move a temporary to new temporary

    T operator<< (int shift) const { return ((S&)*this <<= shift).result ( ); }
    T operator>> (int shift) const { return ((S&)*this >>= shift).result ( ); }

    T operator+ (const S& v) const { return ((S&)*this += v).result ( ); }
    T operator- (const S& v) const { return ((S&)*this -= v).result ( ); }
};

请注意,自2001年以来,此方法已经证明了它在各种综合数据类型(包括字符串,数组,大整数等)中的正确性和有效性,因此它无需参考C ++ 11特性,并且依赖于没有未定义的语言功能。

1 个答案:

答案 0 :(得分:2)

你的假设似乎有误。 C ++支持左侧或右侧的右值。

有两种方法可以做到这一点。

struct noisy {
  noisy() { std::cout << "ctor()\n"; };
  noisy(noisy const&) { std::cout << "ctor(const&)\n"; };
  noisy(noisy &&) { std::cout << "ctor(&&)\n"; };
  noisy& operator=(noisy const&) { std::cout << "asgn(const&)\n"; return *this; };
  noisy& operator=(noisy &&) { std::cout << "asgn(&&)\n"; return *this; };
  ~noisy() { std::cout << "dtor\n"; };
};
struct Bob:noisy {
  int val = 0;
  Bob(int x=0):val(x) {}
  Bob(Bob&&)=default;
  Bob(Bob const&)=default;
  Bob& operator=(Bob&&)=default;
  Bob& operator=(Bob const&)=default;
  friend Bob operator+( Bob lhs, Bob const& rhs ) {
    lhs += rhs;
    return lhs;
  }
  friend Bob& operator+=( Bob& lhs, Bob const& rhs ) {
    lhs.val += rhs.val;
    return lhs;
  }
  friend Bob operator+=( Bob&& lhs, Bob const& rhs ) {
    lhs += rhs; // uses & overload above
    return std::move(lhs);
  }
};

Bob使用朋友操作员基本上做你想要的。

这是我的首选解决方案,朋友运营商比成员运营商更加对称。

struct Alice:noisy {
  int val = 0;
  Alice(int x=0):val(x) {}
  Alice(Alice&&)=default;
  Alice(Alice const&)=default;
  Alice& operator=(Alice&&)=default;
  Alice& operator=(Alice const&)=default;
  Alice operator+( Alice const& rhs ) const& {
    return Alice(*this) + rhs;
  }
  Alice operator+( Alice const& rhs ) && {
    *this += rhs;
    return std::move(*this);
  }
  Alice& operator+=( Alice const& rhs )& {
    val += rhs.val;
    return *this;
  }
  Alice operator+=( Alice const& rhs )&& {
    *this += rhs;  // uses & overload above
    return std::move(*this);
  }
};

Alice使用成员函数执行相同操作。成员函数通过朋友操作符存在常见问题。

请注意在成员函数参数之后使用&&&以及const& 。这在随意讨论中被称为“*this”的“右值参考”功能。它允许您根据正在使用的对象的r / l值来选择哪个重载。

测试代码:

Bob bob;
Bob b2 = Bob{3}+bob;

Alice alice;
Alice a2 = Alice{3}+alice;

Live example

在这两种情况下,都不会复制任何对象。

请注意,我认为添加是不对称的(尽管对状态使用int)。如果是,你可以做另一种效率,其中lhs是非右值而rhs是。