编程参考 - std::exchange和std::swap的区别

这两个功能是C++ standard library中的Standard template library中的一部分。容易混淆,我们来看下它们的区别。

exchange:

这个函数是一个返回原先值的set函数。

std::exchange is a setter returning the old value. 

int z = std::exchange(x, y);

After this line of code executes:

* x is assigned the value of y,

* z is assigned the value that x had initially.

使用伪代码(pseudocode)表示的话,exchange的意思就是:

z <- x <- y

x(第一个参数)的值作为返回值赋值给z;y(第二个参数)的值复制给X。

常用语法定义如下:

template< class T, class U = T >

T exchange( T& obj, U&& new_value );

将第二个参数的值赋给第一个值,并返回第一个参数的旧值。

#include <utility>

int main()

{

  int x = 2;

  auto y = std::exchange(x, 4);

  // y == 2;

  // x == 4;

}

swap:

伪代码表示含义:

A -> B

B <- A

交换两个变量的值。

Semantic and syntax,语义和语法:

最通常的使用语法如下:

template< class T >

void swap( T& a, T& b );

对模板T类型的要求:

T must meet the requirements of CopyConstructible and CopyAssignable (until C++11),MoveConstructible and MoveAssignable (since C++11)

注意swap是不返回值的。这里的参数是对象的引用。

#include <utility>

int main()

{

  int i = 3;

  int j = 4;

  std::swap(i, j);

  // i == 4

  // j == 3

}

$ g++ -o test std.c -std=c++14

swap需要他的参数都不是常量引用,要能转换为一个non-const reference,所以不能使用swap(i, 4),编译会不通过。

使用exchange的场景举例:The “swap-and-iterate” pattern 

这种模式可以使用exchange来做。在很多event-driven的架构中会使用。一般会有一个vector的事件需要分发(dispatch), 或者等同的意思,需要调用相应的callback。 但我们希望事件处理程序能够产生自己的事件,以便进行延迟分派。(But we want event handlers to be able to produce events of their own for deferred dispatch.)

代码如下:

class Dispatcher {

    // We hold some vector of callables that represents

    // events to dispatch or actions to take

    using Callback = /* some callable */;

    std::vector<Callback> callbacks_;

    // Anyone can register an event to be dispatched later

    void defer_event(const Callback& cb) {

        callbacks_.push_back(cb);

    }

    // All events are dispatched when we call process

    void process() {

        std::vector<Callback> tmp{};

        using std::swap; // the "std::swap" two-step

        swap(tmp, callbacks_);

        for (const auto& callback : tmp) {

            std::invoke(callback);

        }

    }

};

这就是 "swap-and-iterate "模式。这个回调类内部调用 defer_event 并因此产生自己的事件是安全的:我们使用 tmp,这样调用 defer_event 就不会使循环中的迭代器失效。

但是,我们在这里做的工作比必要的要多一些,而且还犯了 "ITM antipattern(initialize-then-modify)"的错误。首先,我们构造了一个空vector (tmp),然后,通过 swap,我们有 3 个move assignments,然后才开始迭代。

使用std::exchange进行重构可以解决这些问题:

class Dispatcher {

    // ...

    // All events are dispatched when we call process

    void process() {

        for (const auto& callback : std::exchange(callbacks_, {}) {

            std::invoke(callback);

        }

    }

};

现在,我们不必再声明一个临时量。在 std::exchange 中,我们只有一个移动构造和一个移动赋值,比 swap 节省了一次移动。我们不需要理解 "std::swap 两步法"所涉及的 ADL。我们不需要 tmp,只需要一种表达empty vector的方法,在这里就是 {}。编译器非常善于优化对 std::exchange 的调用,所以我们当然能得到我们通常期望的拷贝消除(copy elision)。因此,代码整体上更加简洁、快速(concise, faster),并提供了与之前相同的安全性。

从这个角度看exchange就是用来调整一个变量的值来使用的。就像我们一直在用的i++, 后缀操作符,在使用完i的值后,再对i的值进行修改。

参考:

1,C++ Weekly: Ask C++ Weekly: `std::exchange` vs `std::swap`

https://youtu.be/GEbPRS81py4?si=9tvUhpGjKstzCog7

2,cppreference.com

std::exchange - cppreference.com

std::swap - cppreference.com

3,std::exchange是干什么的

What std::exchange does, and how to remember it - Fluent C++ (fluentcpp.com)

4,std::exhange的好处

std::exchange Patterns: Fast, Safe, Expressive, and Probably Underused - Fluent C++ (fluentcpp.com)

`std::atomic` 是 C++11 标准库中的一个模板类,用于提供原子操作,保证在多线程编程中对共享数据的访问安全,避免数据竞争不一致性问题 [^1]。以下是关于 `std::atomic` 的一些常见用法: ### 基本类型的原子操作 对于基本数据类型(如 `int`、`bool` 等),可以直接使用 `std::atomic` 进行原子操作。 ```cpp #include <iostream> #include <atomic> #include <thread> std::atomic<int> atomicCounter(0); void increment() { for (int i = 0; i < 10000; ++i) { atomicCounter.fetch_add(1, std::memory_order_relaxed); } } int main() { std::thread t1(increment); std::thread t2(increment); t1.join(); t2.join(); std::cout << "Final counter value: " << atomicCounter.load(std::memory_order_relaxed) << std::endl; return 0; } ``` 在上述代码中,`std::atomic<int> atomicCounter(0)` 定义了一个原子整型变量 `atomicCounter` 并初始化为 0。`fetch_add` 方法用于原子地增加计数器的值,`load` 方法用于原子地读取计数器的值。 ### 自定义类型的原子操作 对于自定义类型(类或结构体),需要满足一定的条件才能使用 `std::atomic`。自定义类型必须是 `TriviallyCopyable` 类型,即可以通过简单的内存复制操作进行复制。 ```cpp #include <iostream> #include <atomic> struct Point { int x; int y; }; std::atomic<Point> atomicPoint; int main() { Point p = {1, 2}; atomicPoint.store(p, std::memory_order_relaxed); Point result = atomicPoint.load(std::memory_order_relaxed); std::cout << "Point: (" << result.x << ", " << result.y << ")" << std::endl; return 0; } ``` 在这个例子中,`Point` 结构体是 `TriviallyCopyable` 类型,因此可以使用 `std::atomic<Point>` 进行原子操作。`store` 方法用于原子地存储一个 `Point` 对象,`load` 方法用于原子地读取 `Point` 对象。 ### 原子比较交换操作 `compare_exchange_weak` `compare_exchange_strong` 是两个重要的原子操作,用于实现无锁算法。 ```cpp #include <iostream> #include <atomic> std::atomic<int> atomicValue(10); void compareAndSwap() { int expected = 10; int desired = 20; bool success = atomicValue.compare_exchange_weak(expected, desired); if (success) { std::cout << "Value was successfully updated." << std::endl; } else { std::cout << "Value was not updated. Current value: " << atomicValue.load() << std::endl; } } int main() { compareAndSwap(); return 0; } ``` `compare_exchange_weak` 尝试将原子变量的值与 `expected` 进行比较,如果相等则将其更新为 `desired`。如果比较失败,`expected` 将被更新为原子变量的当前值。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜流冰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值