C++14之std::exchange的使用和原理分析

目录

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 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++ 中进行原子值交换。然而,在使用它时,需要权衡其提供的原子性与可能的性能影响。

参考

std::exchange - cppreference.com

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
std::atomic_bool是C++14中的原子布尔类型。它提供了原子操作,以确保多线程环境下的安全性。它可以用于实现锁、同步和并发算法等。使用std::atomic_bool可以保证对布尔类型的读写操作在多线程环境中是原子的,即不会发生竞态条件。 在C++14中,std::atomic_bool比std::atomic_flag功能更全,可以使用非原子的bool来赋值和初始化。例如,你可以这样使用std::atomic_bool: std::atomic_bool b(true); b = false; 这样就可以对b进行原子的赋值操作。 需要注意的是,std::atomic_flag由于限制性甚至不能用作一个通用的布尔标识,所以最好还是使用std::atomic_bool。在C语言中,也可以使用自旋锁来实现多线程的同步。以下是一个使用自旋锁实现的例子: #include <thread> #include <vector> #include <iostream> #include <atomic> std::atomic_flag lock = ATOMIC_FLAG_INIT; void f(int n) { for (int cnt = 0; cnt < 5; cnt++) { while (lock.test_and_set(std::memory_order_acquire)) ; // 自旋 std::cout << "Thread " << n << " count:" << cnt << std::endl; lock.clear(std::memory_order_release); // 释放锁 } } int main(int argc, char* argv[]) { std::vector<std::thread> v; for (int n = 0; n < 4; n++) { v.emplace_back(f, n); //使用参数进行初始化 } for (auto& t : v) { t.join(); //等待线程结束 } system("pause"); return 0; } 这个例子中,使用std::atomic_flag作为锁,通过test_and_set()和clear()函数来获得锁和释放锁。 总结来说,std::atomic_bool是C++14中的原子布尔类型,可以用于多线程环境下的安全操作。在C语言中,可以使用自旋锁来实现多线程的同步。<span class="em">1</span><span class="em">2</span><span class="em">3</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值