C++14 exchange

Share on:
400 Words | Read in about 2 Min | View times

Overview

C++14开始新增了std::exchange函数,基于移动语义对资源进行转移,省去了拷贝的消耗。新增的这个函数名表面上看起来是“交换”,但实际上并不是两者进行交换,而是将后者转移给前者,后者没有变化。本节内容介绍std::exchangestd::swap实现上的区别。

本系列文章将包括以下领域:

本章其他内容请见 《现代C++》

std::exchange

std::exchange虽然名为交换,但不是真正意义上的交换。更多的是实现将其中一个元素的资源转移给另一个元素,而进行转移的元素保持不变。

std::exchange声明原型

C++14开始新增std::exchange,定义在标准库头文件<utility>中,声明原型为:

 1//C++14声明原型
 2template <class T, class U = T>
 3T exchange(T& obj, U&& new_value);
 4
 5//C++20声明原型
 6template <class T, class U = T>
 7constexpr T exchange(T& obj, U&& new_value);
 8
 9//C++23声明原型
10template <class T, class U = T>
11constexpr T exchange(T& obj, U&& new_value) noexcept(
12    std::is_nothrow_move_constructible_v<T> &&
13    std::is_nothrow_assignable_v<T&, U>
14);

这个函数使用new_value替换obj的值,并返回obj的原值。

注意,经过这个函数操作后,obj的值变化了,但是new_value的值并没有变化。

另外类型T必须支持移动构造,且支持从类型U移动赋值到类型T的对象上。

std::exchange源码解析

接下来看一下g++源码的实现:

 1//<utility>
 2template <typename _Tp, typename _Up = _Tp>
 3constexpr inline _Tp exchange(_Tp& __obj, _Up&& __new_val)
 4{
 5    return std::__exchange(__obj, std::forward<_Up>(__new_val));
 6}
 7
 8//<move.h>
 9template <typename _Tp, typename _Up = _Tp>
10constexpr inline _Tp __exchange(_Tp& __obj, _Up&& __new_val)
11{
12    _Tp __old_val = std::move(__obj);
13    __obj = std::forward<_Up>(__new_val);
14    return __old_val;
15}

形参__new_val通过完美转发传递到内部函数std::__exchange中,内部实现就是把__obj对象移到赋值给一个临时变量,然后自身使用__new_val进行赋值,最后返回临时变量。返回临时变量的过程会进行NRVO返回值优化。

示例代码

  • std::exchange可以用来实现移动构造函数和移动赋值操作符
 1struct A
 2{
 3    int foo;
 4
 5    //利用exchange返回的是other.foo的原值,用原值初始化foo,同时other.foo的值被重置
 6    A(A&& other)
 7        : foo(std::exchange(other.foo, 0))
 8    { }
 9
10    A& operator=(A&& other)
11    {
12        //利用exchange返回的是other.foo的原值,用原值赋值给foo,同时other.foo的值被重置
13        if (this != &other)
14            foo = std::exchange(other.foo, 0);
15        return *this;
16    }
17};
  • 其他例子
 1#include <iostream>
 2#include <vector>
 3#include <utility>
 4
 5void f() { std::cout << "f()" << std::endl; }
 6
 7int main() {
 8    //利用exchange来初始化vector
 9    std::vector<int> v;
10    std::exchange(v, {1, 2, 3, 4});
11    for (int n : v) {
12        std::cout << n << ", ";
13    }
14    std::cout << std::endl; //1, 2, 3, 4,
15
16    //exchange也可作用于函数指针
17    void (*func)();
18    std::exchange(func, f); //等价于std::exchange(func, static_cast<void(*)()>(f));
19    func(); //f()
20
21    //exchange实现斐波那契数列
22    for (int a = 1, b = 1; a < 10; a = std::exchange(b, a + b))
23    {
24        std::cout << a << ", ";
25    }
26    std::cout << std::endl; //1, 1, 2, 3, 5, 8,
27    return 0;
28}

std::swap

std::swap是实现两个元素互换的函数,是真正意义上的交换,交换完之后两个元素都发生了改变。

std::swap声明原型

std::swap,定义在标准库头文件<utility>中,声明原型为:

 1//C++11-C++20前的声明原型
 2template <class T>
 3void swap(T& a, T& b) noexcept(
 4    std::is_nothrow_move_constructible<T>::value &&
 5    std::is_nothrow_move_assignable<T>::value
 6);
 7
 8//C++20的声明原型
 9template <class T>
10constexpr void swap(T& a, T& b) noexcept(
11    std::is_nothrow_move_constructible<T>::value &&
12    std::is_nothrow_move_assignable<T>::value
13);

std::swap源码解析

接下来看一下g++源码的实现:

1//<utility> -> <move.h>
2template<typename _Tp>
3constexpr inline void swap(_Tp& __a, _Tp& __b)
4{
5    _Tp __tmp = std::move(__a);
6    __a = std::move(__b);
7    __b = std::move(__tmp);
8}

总结

  • std::exchange()使用新值替换旧值,并返回旧值,新值不变,仅是资源的转移,而不是的交换。

  • std::swap()才是真正的交换,两个元素的值进行了互换。

Prev Post: 『C++读写锁』
Next Post: 『C++14更多新特性』