目录
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 constructor
MyClass(MyClass&& other) noexcept : ptr_(std::exchange(other.ptr_, nullptr)) {}
// Move assignment operator
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete ptr_; // delete resource
ptr_ = 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
12
1, 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++ 中进行原子值交换。然而,在使用它时,需要权衡其提供的原子性与可能的性能影响。
参考