C++26 新特性 std::indirect 简化 PImpl
The PImpl idiom and the C++26 std:indirect type
PImpl 惯用法通过不透明指针隐藏实现细节,有效降低编译依赖。传统实现需手动遵循 Rule of Five 处理资源,且常面临 const 属性未传递及移动后指针悬空的问题。使用 std::unique_ptr 虽能简化内存管理,但无法彻底解决这些语义缺陷。C++26 引入的 std::indirect 类型专为解决此类痛点而生,它像值类型一样行为,自动处理深拷贝、完美传递 const 属性,并保证非空状态。相比 raw pointer 和 unique_ptr,std::indirect 让 PImpl 实现更简洁、更安全,同时提供了 valueless_after_move 方法来优雅处理移动后的状态检查。
std::indirect 旨在用于动态分配但需表现得像值类型的类成员,它被设计用来替代那些语义不合适的 std::unique_ptr。
HN 评论区
97- amluto
我有个叫 EImpl 的小类,有点像 std::indirect,但它嵌入(embed)实现而不是指向它。它接受三个模板参数:一个嵌入的结构体、一个大小和一个对齐方式。它会 static_assert 确保嵌入的结构体符合指定的大小和对齐要求,并以几乎零开销将其嵌入。使用起来和其他任何 pImpl 技巧一样简单。
- Panzerschrek
> 永不空:它始终持有值,除非处于移动后状态
我想知道为什么 C++ 不能以同样的方式实现“非空”的 unique_ptr 版本?据我所知,反对实现它的主要论点是这根本做不到,因为移动后的 unique_ptr 仍然可能为空。
- ryani
遗憾的是,在 C++ 中很难完整地实现 pimpl 惯用法。
pimpl 惯用法其实是一种 C 语言惯用法:头文件声明一个不透明结构体,并声明接受该结构体指针的函数原型。在 C 中,OP 的例子大概长这样:
// widget.h
typedef struct Widget_t Widget; /* 不透明! */
Widget* Widget_Create(const string* pName);
Widget* Widget_Clone(Widget*);
void Widget_Destroy(Widget*);
void Widget_click(Widget*);
int Widget_clickCount(const Widget*);
const string* Widget_label(const Widget*);
// widget.c
struct Widget_t {
int clicks;
string *name;
};
// ... 来自 .h 的函数实现 ...
具体来说,在 C 中 `Widget` 直接拥有 `clicks` 和 `name` 作为字段。
但在 C++ 中,我们喜欢使用对象上的方法,而要做到这一点,你需要类声明在当前作用域内,这意味着你的当前编译单元必须看到 Widget 的所有数据成员。实际上,这意味着如果你试图在 C++ 中使用“pimpl”,你会像 OP 那样,在类内部放一个指向不透明类型的指针。
然而,这并不是同一回事。方法是通过 `this` 指针调用的,这意味着每次访问内部结构体都会增加一次额外的指针解引用。这就是为什么这不算真正的 pimpl——每次访问都多浪费了一次解引用。
你可以在当前的 C++ 中实现真正的 pimpl,但这需要大量的样板代码,并且严重依赖编译器的内联。一个实现 […]
- zabzonk
嗯。大家真的那么频繁地使用 PIMPL 吗(我用过,但很少),以至于我们需要标准库的支持(以及测试、文档、理解)?只是问问。
- whizzter
模块(modules)进展如何?pimpl 不主要是为了规避包含整个世界带来的开销吗?
我在想为什么他把默认方法放在 cpp 里,有什么特别的原因吗?
我确实意识到 indirect 版本必须放在 cpp 里,因为头文件不知道 impl 类的定义,也就不知道如何拷贝。