rvalue reference with assignement operator

我们两清 提交于 2019-12-06 10:21:43

问题


In this article http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/comment-page-1/#comment-1877 :

T& T::operator=(T const& x) // x is a reference to the source
{ 
    T tmp(x);          // copy construction of tmp does the hard work
    swap(*this, tmp);  // trade our resources for tmp's
    return *this;      // our (old) resources get destroyed with tmp 
}

but in light of copy elision, that formulation is glaringly inefficient! It’s now “obvious” that the correct way to write a copy-and-swap assignment is:

T& operator=(T x)    // x is a copy of the source; hard work already done
{
    swap(*this, x);  // trade our resources for x's
    return *this;    // our (old) resources get destroyed with x
}

It is said that we should write assignement operator with argument by value rather than by const reference for copy ellision consideration.

With Rvalue reference feature, is it better to write assignement operator like below ? :

T& operator=(T&& x)  
{
    swap(*this, x);
    return *this;
}

there is finally no difference ?


回答1:


You want to operate on a copy, otherwise you're removing information from your original object. The idea is to remove information from a temporary copy. It's not highly intuitive but it allows you to use the existing copy constructor and destructor implementations to do the hard work of op=.

Copy elision is not relevant, because it can't be performed when a copy is semantically required.

Operating on an rvalue reference might be ok, because if you're calling op= with an rvalue expression as the RHS operand, then it's probably a temporary object and the calling scope probably doesn't want/need to use it any more. However, it's not your op='s job to assume that.

Your middle approach is canonical.

T& operator=(T x)    // x is a copy of the source; hard work already done
{
    swap(*this, x);  // trade our resources for x's
    return *this;    // our (old) resources get destroyed with x
}



回答2:


Some types do better with the swap/assign idiom than others. Here's one that doesn't do well:

#include <cstddef>
#include <new>
#include <utility>

template <class T>
class MyVector
{
    T* begin_;
    T* end_;
    T* capacity_;

public:
    MyVector()
        : begin_(nullptr),
          end_(nullptr),
          capacity_(nullptr)
        {}

    ~MyVector()
    {
        clear();
        ::operator delete(begin_);
    }

    MyVector(std::size_t N, const T& t)
        : MyVector()
    {
        if (N > 0)
        {
            begin_ = end_ = static_cast<T*>(::operator new(N*sizeof(T)));
            capacity_ = begin_ + N;
            for (; N > 0; --N, ++end_)
                ::new(end_) T(t);
        }
    }

    MyVector(const MyVector& v)
        : MyVector()
    {
        std::size_t N = v.size();
        if (N > 0)
        {
            begin_ = end_ = static_cast<T*>(::operator new(N*sizeof(T)));
            capacity_ = begin_ + N;
            for (std::size_t i = 0; i < N; ++i, ++end_)
                ::new(end_) T(v[i]);
        }
    }

    MyVector(MyVector&& v)
        : begin_(v.begin_),
          end_(v.end_),
          capacity_(v.capacity_)
    {
        v.begin_ = nullptr;
        v.end_ = nullptr;
        v.capacity_ = nullptr;
    }

#ifndef USE_SWAP_ASSIGNMENT

    MyVector& operator=(const MyVector& v)
    {
        if (this != &v)
        {
            std::size_t N = v.size();
            if (capacity() < N)
            {
                clear();
                ::operator delete(begin_);
                begin_ = end_ = static_cast<T*>(::operator new(N*sizeof(T)));
                capacity_ = begin_ + N;
            }
            std::size_t i = 0;
            T* p = begin_;
            for (; p < end_ && i < N; ++p, ++i)
                (*this)[i] = v[i];
            if (i < N)
            {
                for (; i < N; ++i, ++end_)
                    ::new(end_) T(v[i]);
            }
            else
            {
                while (end_ > p)
                {
                    --end_;
                    end_->~T();
                }
            }
        }
        return *this;
    }

    MyVector& operator=(MyVector&& v)
    {
        clear();
        swap(v);
        return *this;
    }

#else

    MyVector& operator=(MyVector v)
    {
        swap(v);
        return *this;
    }

#endif

    void clear()
    {
        while (end_ > begin_)
        {
            --end_;
            end_->~T();
        }
    }

    std::size_t size() const
        {return static_cast<std::size_t>(end_ - begin_);}
    std::size_t capacity() const
        {return static_cast<std::size_t>(capacity_ - begin_);}
    const T& operator[](std::size_t i) const
        {return begin_[i];}
    T& operator[](std::size_t i)
        {return begin_[i];}
    void swap(MyVector& v)
    {
        std::swap(begin_, v.begin_);
        std::swap(end_, v.end_);
        std::swap(capacity_, v.capacity_);
    }
};

template <class T>
inline
void
swap(MyVector<T>& x, MyVector<T>& y)
{
    x.swap(y);
}

#include <iostream>
#include <string>
#include <chrono>

int main()
{
    MyVector<std::string> v1(1000, "1234567890123456789012345678901234567890");
    MyVector<std::string> v2(1000, "1234567890123456789012345678901234567890123456789");
    typedef std::chrono::high_resolution_clock Clock;
    typedef std::chrono::duration<double, std::micro> US;
    auto t0 = Clock::now();
    v2 = v1;
    auto t1 = Clock::now();
    std::cout << US(t1-t0).count() << " microseconds\n";

}

Here are results off of my machine:

$ clang++ -std=c++0x -stdlib=libc++ -O3  test.cpp
$ a.out
23.763 microseconds
$ a.out
23.322 microseconds
$ a.out
23.46 microseconds
$ clang++ -std=c++0x -stdlib=libc++ -O3 -DUSE_SWAP_ASSIGNMENT test.cpp
$ a.out
176.452 microseconds
$ a.out
219.474 microseconds
$ a.out
178.15 microseconds

My point: Don't fall into the trap of believing in a silver bullet, or the "one right way to do everything". And the copy/swap idiom is way oversold. It is sometimes appropriate. But in no way is it always appropriate. There is no substitute for careful design, and careful testing.




回答3:


With Rvalue reference feature, is it better to write assignement operator like below ?

It is not better, since these two operators are different (see rule of five).

The 1st (T& T::operator=(T const& x)) is for assigning l-values, while the 2nd (T& operator=(T&& x)) is for r-values. Take a note that this would fail to compile if you had only the 2nd implemented :

#include <iostream>

struct T
{
  T(int v):a(v){}

  T& operator=( const T& t)
  {
    std::cout<<"copy"<<std::endl;
    a=t.a;
    return *this;
  }
  T& operator=( T&& t)
  {
    std::cout<<"move"<<std::endl;
    a=std::move(t.a);
    return *this;
  }

  int a;
};

void foo( const T &t)
{
  T tmp(2);
  std::cout<<tmp.a<<std::endl;
  tmp=t;
  std::cout<<tmp.a<<std::endl;
}
void bar(T &&t)
{
  T tmp(2);
  std::cout<<tmp.a<<std::endl;
  tmp=std::move(t);
  std::cout<<tmp.a<<std::endl;
}

int main( void )
{
  T t1(1);
  std::cout<<"foo"<<std::endl;
  foo(t1);
  std::cout<<"bar"<<std::endl;
  bar(T(5));
}


来源:https://stackoverflow.com/questions/8848363/rvalue-reference-with-assignement-operator

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!