目录
1.概述
2.使用
2.1.交换操作
2.2.移动语义
3.原理
4.综合示例
5.总结
1.概述
std::exchange
是 C++ 标准库中的一个实用函数,它的主要作用是替换一个对象的值,并返回该对象的旧值。这个函数在 C++14 中引入,主要用于简化和优化代码。
它的原型定义如下:
这个函数接受两个参数:一个是要替换值的对象 obj
,另一个是新的值 new_value
。函数将 obj
的值替换为 new_value
,并返回 obj
的旧值。
注意: T
必须满足可移动构造 (MoveConstructible) 。而且必须能移动赋值U
类型对象给T
类型对象
2.使用
2.1.交换操作
在std::exchage未出现之前, 我们交换两个变量的值,需要先定义一个临时的中间变量,但是使用std::exchange,你可以更简洁地完成这个操作;这对于实现一些特定的算法,尤其是需要保持变量旧值的算法时非常有用。示例如下:
#include <iostream>
#include <utility>
#include <string>int main() {std::string name = "Alice";std::string new_name = "Bob";std::string old_name = std::exchange(name, new_name);std::cout << "Old name: " << old_name << std::endl;std::cout << "New name: " << name << std::endl;return 0;
}
2.2.移动语义
std::exchange
在处理移动语义时非常有用。例如,你可能想要在类的移动构造函数或移动赋值运算符中使用它:
class MyClass {
public:// Move constructorMyClass(MyClass&& other) noexcept : ptr_(std::exchange(other.ptr_, nullptr)) {}// Move assignment operatorMyClass& operator=(MyClass&& other) noexcept {if (this != &other) {delete ptr_; // delete resourceptr_ = std::exchange(other.ptr_, nullptr); // acquire new resource}return *this;}private:int* ptr_;
};
在这个例子中,std::exchange
用于获取other
的资源(ptr_
),并将other.ptr_
设置为nullptr
,以防止other
在析构时删除资源。
3.原理
std::exchange
的实现原理相对简单。它首先返回 obj
的当前值(即“旧值”),然后将 obj
的值设置为 new_val
。这通常是通过原子操作完成的,以确保在多线程环境中操作的原子性。然而,具体的实现可能因编译器和平台而异。
在 vs2019 中,std::exchange
的实现如下:
// FUNCTION TEMPLATE exchange
template <class _Ty, class _Other = _Ty>
_CONSTEXPR20 _Ty exchange(_Ty& _Val, _Other&& _New_val) noexcept(conjunction_v<is_nothrow_move_constructible<_Ty>, is_nothrow_assignable<_Ty&, _Other>>) /* strengthened */ {// assign _New_val to _Val, return previous _Val_Ty _Old_val = static_cast<_Ty&&>(_Val);_Val = static_cast<_Other&&>(_New_val);return _Old_val;
}
这里有几个关键点:
1) noexcept(conjunction_v<is_nothrow_move_constructible<_Ty>, is_nothrow_assignable<_Ty&, _Other>>)确保如果赋值操作是 noexcept 的,那么整个 std::exchange 函数也是 noexcept 的。这有助于优化和异常安全性。conjunction_v是逻辑与。
2) static_cast<_Ty&&>(_Val)相当于std::move(_Val) 用于获取 _Val 的旧值,并将其转换为右值引用,以便在可能的情况下利用移动语义。
3)_Val = static_cast<_Other&&>(_New_val); 将新值赋给 _Val 相当于_Val = std::forward<_Other>(_New_val)。std::forward 用于保持 _New_val的值类别(左值或右值)。
4.综合示例
#include <iostream>
#include <iterator>
#include <utility>
#include <vector>class stream
{
public:using flags_type = int;public:flags_type flags() const { return flags_; }/// 以 newf 替换 flags_ 并返回旧值。flags_type flags(flags_type newf) { return std::exchange(flags_, newf); }private:flags_type flags_ = 0;
};void f() { std::cout << "f()"; }int main()
{stream s;std::cout << s.flags() << '\n';std::cout << s.flags(12) << '\n';std::cout << s.flags() << "\n\n";std::vector<int> v;// 因为第二模板形参有默认值,故能以花括号初始化式列表为第二实参。// 下方表达式等价于 std::exchange(v, std::vector<int>{1, 2, 3, 4});std::exchange(v, {1, 2, 3, 4});std::copy(begin(v), end(v), std::ostream_iterator<int>(std::cout, ", "));std::cout << "\n\n";void (*fun)();// 模板形参的默认值亦使得能以通常函数为第二实参。// 下方表达式等价于 std::exchange(fun, static_cast<void(*)()>(f))std::exchange(fun, f);fun();std::cout << "\n\n斐波那契数列: ";for (int a{0}, b{1}; a < 100; a = std::exchange(b, a + b))std::cout << a << ", ";std::cout << "...\n";
}
输出:
0
0
121, 2, 3, 4,f()斐波那契数列: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...
5.总结
1) 原子性:std::exchange 提供了原子操作,这意味着在交换过程中,其他线程不会看到中间状态。这对于需要精确控制数据状态的多线程应用程序非常有用。
2) 返回值:std::exchange 返回交换前的值。这允许你在一个操作中同时获取和设置值。
3) 用途广泛:除了直接的数据交换,std::exchange 还可以与算法和其他模板一起使用,以提供更复杂的操作。
4) 与 std::swap 的区别:std::swap 也是用于交换两个值的函数,但它不保证原子性。在单线程环境中,它们可以互换使用,但在多线程环境中,std::exchange 更为安全。
5) 性能考虑:由于 std::exchange 提供了原子性保证,它可能比非原子操作(如简单的赋值)更慢。因此,在不需要原子性的情况下,使用简单的赋值或其他非原子操作可能更为高效。
6) 与 C++ 标准库的其他部分集成:std::exchange 可以与 C++ 的其他部分(如算法、容器和迭代器)无缝集成,提供灵活的编程选项。
总之,std::exchange 是一个强大且灵活的工具,可以用于在 C++ 中进行原子值交换。然而,在使用它时,需要权衡其提供的原子性与可能的性能影响。
参考
std::exchange - cppreference.com