Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

使用交换操作实现赋值运算符重载 #73

Open
yuguojia opened this issue Jun 12, 2022 · 0 comments
Open

使用交换操作实现赋值运算符重载 #73

yuguojia opened this issue Jun 12, 2022 · 0 comments

Comments

@yuguojia
Copy link

很多时候我们会遇到两个对象值的交换,例如std::sort使用的排序算法就是基于变量交换的,std::reverse也用到了交换。交换效率的高低决定了这些算法的执行时间。

然而,交换操作往往会带来不必要的复制。在<utility>中定义了std::swap可以实现变量的交换,我们先看看std::swap的实现。

std::swap的一种实现

template<typename T>
void swap(T &a,T &b) noexcept
{
    T temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}

参见L6-引用与复制PPT,这样实现swap,可以避免3次不必要的拷贝操作,只是有一个前提要求——类型T必须同时重载了移动构造函数和移动赋值运算符。如果类型T只重载了拷贝构造函数和拷贝赋值运算符,那么使用swap,多余的拷贝操作仍无法避免。

实现自己的类的swap函数

在类型T没有实现移动操作的情况下,我们可以自己实现swap函数。

以第三次作业http://115.182.62.169:8000/contest/106/problem/80为例,交换两个Vector,只需要交换array指针和成员变量len、capacity即可,实现起来非常容易:

void swap(Vector &a,Vector &b)
{
    using std::swap;
    // 用std::swap交换变量的值
    swap(a.array,b.array);
    swap(a.len,b.len);
    swap(a.capacity,b.capacity);
}

你可能会问,实现了这个swap函数之后,真的就能够加快std::reverse等算法了吗?答案是肯定的。我们不妨来看看std::reverse的源码是怎样的:
(一种可能的实现)

template<class BidirIt>
void reverse(BidirIt first, BidirIt last)
{
    while ((first != last) && (first != --last)) {
        std::iter_swap(first++, last);
    }
}

可以看到,当需要交换两个元素时,std::reverse用到了std::iter_swap。我们再来看看std::iter_swap的实现:

template<class ForwardIt1, class ForwardIt2>
void iter_swap(ForwardIt1 a, ForwardIt2 b)
{
   using std::swap;
   swap(*a, *b);
}

为什么std::iter_swap的实现没有直接std::swap(*a, *b);,而是先using std::swap;再执行swap呢?

其实,直接使用std::swap(*a, *b);并不会带来任何性能上的优势——我们自定义的swap函数并不在命名空间std内。这样虽然没有编译错误,但是程序根本没有用到我们自己写的swap函数,最后使用的仍然是没有优化过的std::swap

using std::swap;再执行swap就能很好地解决这个问题。如果我们自己写了一个swap,会将std::swap覆盖掉(这是因为函数匹配的原则:选择最为特例化的版本,因而普通函数会比函数模板std::swap优先级高)。std::swap只是在没有专门实现时才会被调用。

这也提示了我们一个关于变量交换的best practice:交换两个变量a,b时,最好用using std::swap; swap(a,b);,而不是直接std::swap(a,b);

"copy-and-swap"技巧

编写自己的swap函数还有一个额外的好处。如果已经写好了类的拷贝构造函数、移动构造函数和swap函数,我们可以轻易完成赋值运算符的重载:

Vector& Vector::operator=(Vector rhs)
{
    swap(*this,rhs);
    return *this;
}

注意第一行rhs是按值传递的,调用了Vector的拷贝/移动构造函数,将等号右边的Vector拷贝/移动到临时变量rhs中。接着,将等号左侧的Vector和rhs交换,这样就相当于把等号右侧的Vector赋值到左侧,而交换后rhs拥有等号左侧Vector的资源。最后,函数返回时,rhs的生命周期结束,对rhs进行析构,就释放了原来等号左侧对象的资源。

而且,这样的代码可以正确处理自赋值的情况。此时rhs会拷贝一份等号右侧对象,再与等号左侧的对象交换,并不会出现任何问题。

参考:
https://blog.csdn.net/wbvalid/article/details/117852626

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant