Ссылка на значение с оператором присваивания - PullRequest
3 голосов
/ 13 января 2012

В этой статье 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
}

Говорят, что мы должны написать оператор присваивания с аргументом по значению, а не по константной ссылке для рассмотрения копирования.

С RValue опорной функции, что лучше писать оператор присваивания, как показано ниже? :

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

наконец, нет никакой разницы?

Ответы [ 3 ]

9 голосов
/ 13 января 2012

Некоторые типы лучше справляются с идиомой swap / assign, чем другие. Вот тот, который не очень хорошо:

#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 голос
/ 13 января 2012

Вы хотите, чтобы работали с копией, в противном случае вы удаляете информацию из исходного объекта. Идея состоит в том, чтобы удалить информацию из временной копии. Он не очень интуитивно понятен, но позволяет вам использовать существующие реализации конструктора копирования и деструктора для выполнения тяжелой работы op=.

Исключение копирования не имеет значения, поскольку оно не может быть выполнено, когда копия семантически требуется.

Работа со ссылкой на rvalue может быть в порядке, потому что если вы вызываете op= с выражением rvalue в качестве операнда 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
}
1 голос
/ 13 января 2012

С RValue справочной функции, это лучше писать оператор присваивания, как показано ниже

Это не лучше, поскольку эти два оператора различны (см. правило пяти ).

1-й (T& T::operator=(T const& x)) предназначен для присвоения l-значений, а 2-й (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));
}
...