网易首页 > 网易号 > 正文 申请入驻

“Rust 思维下的 C++ 编程”:在 C++ 中,如何应用 Rust 中的概念?

0
分享至

【编者按】自从美国白宫对开发者呼吁,“停止使用 C 和 C++,改用 Rust 等内存安全编程语言”后,两方之间从未停止的争论就被推到了一个新高度。而在这之中,也有部分 C++ 开发者提议:或许 Rust 中的一些概念,可以试着运用到 C++ 编程中?

整理 | 郑丽媛

近日,一位开发者(ID:delta242)在 Reddit 上发了一篇长文《在 C++ 中应用 Rust 的概念》,里面提到了一些可用于改善 C++ 代码的 Rust 概念,引来了诸多关注和讨论。

根据他在开篇的介绍,“虽然我不是 Rust 专家,但我很喜欢这门语言的许多概念。在日常编程中,我主要用 C++,而现在我经常会运用一些 Rust 的概念来改善我的 C++ 代码”,可以看出,下面是他亲身实践过的、可用于优化 C++ 代码的 Rust 概念。

在 C++ 中,如何应用 Rust 的概念?

(以下为他的长文翻译:)

(1)带值的枚举

我很喜欢 Rust 的枚举,因为你可以给枚举常量赋值(例如,Option 枚举中有一个没有值的 None 和一个有值的 Some)。在类型理论中,这通常被称为代数数据类型,而在 C++ 中,我们有 variants,可以定义辅助结构体来实现类似的功能:

struct Some { T value; };
struct None { };
using Optional = std::variant ;

(注:这个例子可能有点蠢,因为 std::optional 要好得多。但对于更复杂的类型来说,这具有一定参考意义。)

(2)CRTP 和 Traits

在 Rust 中,Traits 用于定义类型的共享功能。而在 C++ 中,我们可以用 CRTP 在编译时强制类实现特定的函数来实现静态多态性。CRTP 还允许在基类中实现默认功能,我以前曾用这种方法来定义迭代器类型,只要基类实现了 operator[],就可以减少大量模板代码的编写。

(3)字符串格式化

在 C++ 中,如果向 std::format 传递的参数数量多于格式字符串中的占位符,并不会导致编译时错误。我曾经遇到过这样的 bug,例如由于缺少占位符,日志消息中缺少了某些信息,导致与代码中不一致。

而这个情况如果放在 Rust 中,就会产生编译时错误。所以这对于 C++ 来说,将是一个简单而实用的改进,有助于提高代码质量和开发效率。

(4)拥有 Mutex

在 Rust 中,Mutex 类型拥有受保护的值。我非常喜欢这个概念,这样不获取 Mutex 就无法访问受保护的值(这在 C++ 中经常发生)。有一个简单的技巧来实现类似效果,那就是在 C++ 中写一个具有 lock 函数的封装 Mutex 类,该函数将接受一个带有对受保护值的引用的 lambda 表达式作为参数。由于 Rust 中有借用检查器,这样的操作总是安全的,而在 C++ 中,误用很容易再次导致竞争条件,但至少通过这样的封装器,这种情况就不那么容易发生了。

(5)内部可变性

Rust 在安全的情况下会使用内部可变性(即使变量是 const),例如当一个值受 Mutex 保护时。在 C++ 中,我们也可以采用类似的想法,例如“const 表示线程安全”。

(6)IIFE

在 Rust 中,每个作用域都是一个表达式,这样可以很好地将变量限制在更小的作用域中。而在 C++ 中,我们可以用 lamdas 表达式来使用立即调用的函数表达式(IIFE)来达到同样的效果:

auto value = [] {
// Complex initializer
return result;
}(); // notice the invocation

以上,就是我现在能想到的。

“Rust 让我成为了一名更好的 C++ 开发者”

在这篇长文下,不少开发者也分享了自己在 C++ 编程中借鉴 Rust 概念的心得,甚至直言“Rust 让我成为了一名更好的 C++ 开发者”。

(1)“最近,我养成了在 C++ 中使用“match”宏的这个习惯,我很喜欢。”

template struct overloaded : Ts... { using Ts::operator()...; };

template
auto match(Val val, Ts... ts) {
return std::visit(overloaded{ts...}, val);
}

(2)“重载非常好,我觉得它可以成为 STL 的一部分。此外,有了 C++20 模板化的 lambdas,还可以编写一些非常花哨的代码。”

visit(
overloaded {
[] T>(T value) {}
[](auto other) {}
}, value)

对此,一位开发者感慨:“这正是我希望看到的,虽然我不喜欢 Rust,但它确实有一些 C++ 可以借鉴的做法,更安全总归是好的。”

在 C++ 中应用 Rust 概念的一些失败案例

不过与此同时,也有开发者提醒“必须小心”:以 Rust 的 Mutex 为例,当你访问 Mutex 中的数据时,不可能将该指针存储下来,然后在解锁 Mutex 后再访问数据(忽略特殊情况)。你可以在 C++ 中实现一个拥有 Mutex 的类,但编译器不会在意你是否在锁的作用域之外持有一个指向受保护数据的指针,并在未受保护的情况下访问它。

针对这个话题,开源搜索引擎 Meilisearch 的高级工程师 Louis Dureuil 曾写过一篇相关文章《这对 C++ 来说太危险了》:“一些设计模式之所以实用,归功于 Rust 的内存安全性,而在 C++ 中使用则过于危险。”

在文中,Louis Dureuil 分享了他在 C++ 中应用 Rust 概念的失败案例。

当时,他正在用 Rust 编写一个内部库,其中有一个他希望能克隆、而不会复制其中数据的错误类型。在 Rust 中,这需要使用引用计数指针,比如 Rc。他编写了一个错误类型,将其用作可能发生错误的函数的错误变体,继续了他的工作。

struct Error {
data: Rc ,
}

pub type Response = Result ;

fn parse(input: Input) -> Response {
todo!()
}

后来他发现,对某些输入进行解析需要很长时间,于是决定通过通道将输入发送到另一个线程,并通过另一个通道获取响应,这样长时间的解析就不会阻塞主线程。

enum Command {
Input(Input),
Exit,
}

pub enum RequestStatus {
Completed(Response),
Running,
}

pub struct Parser {
command_sender: Sender,
response_receiver: Receiver<(Input, Response)>,
cached_result: HashMap ,
}

impl Parser {
pub fn new() -> Self {
let (command_sender, command_receiver) = channel::();
let (response_sender, response_receiver) = channel::<(Input, Response)>();

std::thread::spawn(move || loop {
match command_receiver.recv() {
Ok(Command::Input(input)) => {
let response = parse(input);
let _ = response_sender.send((input, response));
}
Ok(Command::Exit) => break,
Err(_) => break,
}
});

Self {
command_sender,
response_receiver,
cached_result: HashMap::default(),
}
}

pub fn request_parsing(&mut self, input: Input) -> RequestStatus {
// pump previously received responses
while let Ok((input, response)) = self.response.receiver.try_recv() {
self.cached_result
.insert(input, RequestStatus::Completed(response));
}

let response = match self.cached_result.entry(input) {
Entry::Vacant(entry) => {
self.command_sender
.send(Command::Input(entry.key()))
.unwrap();
entry.insert(RequestStatus::Running)
}
Entry::Occupied(entry) => entry.into_mut(),
};
response.clone()
}
}

然而,在进行这一更改时,Louis Dureuil 收到了以下错误信息:

error[E0277]: `Rc ` cannot be sent between threads safely
--> src/main.rs:58:32
|
58 | std::thread::spawn(move || loop {
| _____________------------------_^
| | |
| | required by a bound introduced by this call
59 | | match command_receiver.recv() {
60 | | Ok(Command::Input(input)) => {
61 | | let response = maybe_make(input);
... |
68 | | }
69 | | });
| |_____________^ `Rc ` cannot be sent between threads safely
|
= help: within `(&'static str, Result )`, the trait `Send` is not implemented for `Rc `
note: required because it appears within the type `Error`
--> src/main.rs:17:16
|
17 | pub struct Error {
| ^^^^^
note: required because it appears within the type `Result `
--> /home/dureuill/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/result.rs:502:10
|
502 | pub enum Result {
| ^^^^^^
= note: required because it appears within the type `(&str, Result )`
= note: required for `Sender<(&'static str, Result )>` to implement `Send`
note: required because it's used within this closure
--> src/main.rs:58:32
|
58 | std::thread::spawn(move || loop {
| ^^^^^^^
note: required by a bound in `spawn`
--> /home/dureuill/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/mod.rs:683:8
|
680 | pub fn spawn (f: F) -> JoinHandle
| ----- required by a bound in this function
...
683 | F: Send + 'static,
| ^^^^ required by this bound in `spawn`

正如编译器所解释的那样,这是因为 Rc 类型不支持在线程之间发送,因为这样会导致数据竞争。实际上,Rc 中的引用计数并不以原子方式进行操作,而是使用常规的整数操作。

为了实现线程安全的引用计数,Rust 提供了另一种类型 Arc,它使用原子引用计数,而将代码修改为使用 Arc 非常简单:

diff --git a/src/main.rs b/src/main.rs
index 04ec0d0..fd4b447 100644
--- a/src/main.rs
+++ b/src/main.rs
 use std::{io::Write, time::Duration};
mod parse {
use std::{
collections::{hash_map::Entry, HashMap},
- rc::Rc,
sync::{
mpsc::{channel, Receiver, Sender},
+ Arc,
},
time::Duration,
};
 mod parse {

#[derive(Clone, Debug)]
pub struct Error {
- data: Rc ,
+ data: Arc ,
}

impl Error {
fn new(data: ExpensiveToCloneDirectly) -> Self {
Self {
- data: Rc::new(data),
+ data: Arc::new(data),
}
}
}

也就是说,只要不需要引用原子操作的计数,就可以使用 Rc。但当需要线程安全时,编译器会强制 Louis Dureuil 切换到 Arc,并带来了原子引用计数的开销。

Louis Dureuil 指出,这个原则也深受 C++ 开发者的喜爱。但与 Rust 完全不同的是,在 C++ 中,标准库中只有带有原子引用计数的 shared_ptr,它相当于 Arc,而不是 Rc——所以,即使你不使用原子操作,也仍要为原子引用计数付出代价。

最后,一句话总结:在 C++ 中适当应用 Rust 概念固然不错,但切记不要根据在 Rust 中会发生的情况,对 C++ 也做出相同的假设。

https://www.reddit.com/r/cpp/comments/1bx7wjm/applying_concepts_from_rust_in_c/

https://blog.dureuill.net/articles/too-dangerous-cpp/

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
突发!韩国股市闪崩,瞬间暴跌超500点!SK海力士、三星电子跳水

突发!韩国股市闪崩,瞬间暴跌超500点!SK海力士、三星电子跳水

每日经济新闻
2026-05-12 17:45:51
黄仁勋真是被白宫彻底封杀了

黄仁勋真是被白宫彻底封杀了

大猫财经Pro
2026-05-12 14:04:28
两年输光2000亿资产的王健林,可能是中国地产的最后赢家

两年输光2000亿资产的王健林,可能是中国地产的最后赢家

流苏晚晴
2026-05-12 19:20:28
以前叫人家强哥,现在请叫植物

以前叫人家强哥,现在请叫植物

阿亮评论
2026-05-12 12:18:33
国乒双冠后收到坏消息!孙颖莎王楚钦全胜开启魔鬼赛程 31岁林高远复出!

国乒双冠后收到坏消息!孙颖莎王楚钦全胜开启魔鬼赛程 31岁林高远复出!

好乒乓
2026-05-12 12:16:38
特斯拉宣布停产,震惊全网!

特斯拉宣布停产,震惊全网!

财经三分钟pro
2026-05-12 15:10:58
美媒披露:阿联酋秘密对伊朗发动军事打击

美媒披露:阿联酋秘密对伊朗发动军事打击

参考消息
2026-05-12 20:36:12
0分,全部0分!两大核心啊!广东队正式淘汰出局

0分,全部0分!两大核心啊!广东队正式淘汰出局

篮球实战宝典
2026-05-12 21:48:38
女子推搡哨兵后续:官媒发声,知情人爆料,恐不止坐牢这么简单

女子推搡哨兵后续:官媒发声,知情人爆料,恐不止坐牢这么简单

千言娱乐记
2026-05-12 15:10:56
俄罗斯副总理诺瓦克:俄罗斯将2026年经济增长预测从1.3%下调至0.4%

俄罗斯副总理诺瓦克:俄罗斯将2026年经济增长预测从1.3%下调至0.4%

财联社
2026-05-12 05:06:20
人没到先点名!特朗普还没落地,内塔尼亚胡竟提前对中国发出警告

人没到先点名!特朗普还没落地,内塔尼亚胡竟提前对中国发出警告

小叨娱乐
2026-05-12 13:39:10
两次嫁给梁靖崑,退圈安心照顾两个儿子,如今丈夫成为大学教授

两次嫁给梁靖崑,退圈安心照顾两个儿子,如今丈夫成为大学教授

往史过眼云烟
2026-05-12 22:04:20
殷桃的“饱满”身材真馋人,一袭抹胸亮片裙气质惊艳,真不怕走光

殷桃的“饱满”身材真馋人,一袭抹胸亮片裙气质惊艳,真不怕走光

蓓小西
2026-05-12 09:52:19
武大开了一个坏头

武大开了一个坏头

燕梳楼频道
2026-05-12 12:31:28
出乎众人预料,中方提前48小时官宣特朗普访华,高市早苗心愿落空

出乎众人预料,中方提前48小时官宣特朗普访华,高市早苗心愿落空

策前论
2026-05-11 18:13:56
腾讯张军:微信“访客功能”已焊死,不会开发,不会提供

腾讯张军:微信“访客功能”已焊死,不会开发,不会提供

界面新闻
2026-05-12 10:29:50
太猖狂!四川凌晨追打事件后续:6人一锅端,被查女子蛮横袭警

太猖狂!四川凌晨追打事件后续:6人一锅端,被查女子蛮横袭警

奇思妙想草叶君
2026-05-12 16:24:04
犹太人忍不了了!英国爆发百万游行,犹太领袖被排除在反犹集会外

犹太人忍不了了!英国爆发百万游行,犹太领袖被排除在反犹集会外

爱吃醋的猫咪
2026-05-12 20:41:46
21年首次晋级世少赛!U17国足一雪前耻,2026五大目标已完成

21年首次晋级世少赛!U17国足一雪前耻,2026五大目标已完成

奥拜尔
2026-05-13 01:56:27
即将官宣!皇马终于敲定新主帅,穆里尼奥提两大条件,球迷松口气

即将官宣!皇马终于敲定新主帅,穆里尼奥提两大条件,球迷松口气

祥谈体育
2026-05-12 16:12:37
2026-05-13 02:56:49
CSDN incentive-icons
CSDN
成就一亿技术人
26533文章数 242284关注度
往期回顾 全部

科技要闻

宇树发布载人变形机甲,定价390万元起

头条要闻

特朗普称将同中方讨论对台军售和黎智英案 外交部回应

头条要闻

特朗普称将同中方讨论对台军售和黎智英案 外交部回应

体育要闻

骑士终于玩明白了?

娱乐要闻

白鹿风波升级!掉粉20万评论区沦陷

财经要闻

利润再腰斩 京东干外卖后就没过过好日子

汽车要闻

吉利银河“TT”申报图曝光 电动尾翼+激光雷达

态度原创

亲子
教育
手机
旅游
军事航空

亲子要闻

有点东西。周宁运动空间

教育要闻

求求你试试「5+1+1」学习法!!!

手机要闻

传iPhone 18 Pro或在内存危机中将继续维持“激进定价”策略

旅游要闻

故宫挤满游客,人人撑伞前行:宁愿热到出汗,也要奔赴紫禁城!

军事要闻

知情人士披露:美国或考虑恢复对伊朗军事行动

无障碍浏览 进入关怀版