带赋值运算符的右值引用

时间:2012-01-13 09:29:24

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

在本文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 
}

但是考虑到副本的缺失,这种表述显然效率低下!现在很明显,编写复制和交换分配的正确方法是:

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
}

据说我们应该通过值而不是const参考来编写具有参数的分配运算符,以便复制椭圆考虑。

使用Rvalue参考功能,编写下面的分配运算符会更好吗? :

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

最终没有区别?

3 个答案:

答案 0 :(得分:9)

有些类型的交换/分配习惯比其他类型更好。这是一个表现不佳的人:

#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";

}

以下是我的机器的结果:

$ 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

我的观点:不要陷入相信银弹的陷阱,或“一切正确的方法”。复制/交换习语是超卖的方式。它有时是合适的。但绝不是永远合适的。精心设计和仔细测试是无可替代的。

答案 1 :(得分:1)

  

使用Rvalue参考功能,编写下面的分配运算符会更好吗?

这不是更好,因为这两个运算符是不同的(见rule of five)。

第一个(T& T::operator=(T const& x))用于分配l值,而第二个(T& operator=(T&& x))用于r值。请注意,如果您只实现了第二个,则无法编译:

#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));
}

答案 2 :(得分:1)

希望对副本进行操作,否则您将从原始对象中删除信息。我们的想法是从临时副本中删除信息。它不是非常直观,但它允许您使用现有的复制构造函数和析构函数实现来完成op=的艰苦工作。

复制省略不相关,因为在语义上需要复制时无法执行。

对右值引用进行操作可能没问题,因为如果你使用rvalue表达式调用op=作为RHS操作数,那么它可能是一个临时对象,而调用范围可能是不想/需要再使用它了。但是,假设这不是你op=的工作。

你的中间方法是规范的。

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
}