带赋值运算符的右值引用

发布于 2024-12-26 20:51:47 字数 1011 浏览 1 评论 0原文

在本文中 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 引用的参数来编写赋值运算符。

使用右值引用功能,像下面这样编写赋值运算符是否更好? :

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

终于没有区别了?

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 ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

我乃一代侩神 2025-01-02 20:51:47

某些类型在交换/分配习惯用法方面比其他类型做得更好。这是一个做得不太好的:

#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

我的观点:不要陷入相信灵丹妙药或“做所有事情的唯一正确方法”的陷阱。复制/交换的说法已经被过度宣传了。有时是适当的。但它并不总是合适的。仔细的设计和仔细的测试是无可替代的。

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.

不知在何时 2025-01-02 20:51:47

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

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

对右值引用进行操作可能没问题,因为如果您使用右值表达式作为 RHS 操作数来调用 op=,那么它可能是一个临时对象和调用范围可能不想/不需要再使用它了。但是,您的 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
}

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
}
梦魇绽荼蘼 2025-01-02 20:51:47

有了右值引用特性,像下面这样写赋值运算符是不是更好?

这并不是更好,因为这两个运算符是不同的(请参阅五规则)。

第一个 (T& T::operator=(T const& x)) 用于分配左值,而第二个 (T& operator=(T&& x)< /code>) 用于 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));
}

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));
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文