执行 PIMPL 时如何避免共享指针开销

NoS*_*tAl -1 c++ pimpl-idiom shared-ptr c++20

AFAIKunique_ptr与 PIMPL 一起使用非常棘手,因为删除器是unique_ptr类型的一部分,因此它不适用于不完整的类型。另一方面,shared_ptr使用动态删除器,因此它可以处理不完整的类型。

shared_ptr无论我是否需要,都存在给我原子操作的性能问题。

我可以使用其他更快的替代方案吗std::?我显然对类型擦除很满意,我说的是原子引用计数的成本。

#include <any>
#include <memory>
#include <iosfwd>

std::shared_ptr<std::fstream> sp;
// unique_ptr requires complete type
// std::unique_ptr<std::fstream> up;
std::any a;

#include <fstream>
int main() {
    // any requires copy_constructible
    // a = std::fstream{};  
    sp = std::make_shared<std::fstream>();
} 
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 我考虑过any,但它不适用于某些类型。
  • 我考虑使用动态删除器的 unique_ptr ,但据我所知构造unique_ptr函数永远不会“告诉”删除器构造的对象是什么(为删除器提供一种学习如何销毁对象的方法)。

PS 我知道很久以前boost::shared_ptr就有宏来禁用原子引用计数,但即使仍然支持我也不想切换到boost::shared_ptr.

Nic*_*las 6

首先,请注意,“原子引用计数”shared_ptr只有在您实际使用时才会出现问题。由于您的类是不可复制的(这对于接口类型来说是有意义的,并且由于您愿意使用unique_ptr,所以它必须是不可复制的),因此计数只会在构造和销毁时修改。所以...你不应该关心; 即使原子引用计数实际上在性能方面很重要,它也将是伴随它的内存分配/取消分配成本的舍入错误。

至于您的实际问题,您在这里收到编译错误的唯一原因是因为您强制编译器unique_ptr<T>从仍然不完整的地方实例化 的析构函数T

在PIMPL 的实际使用中,您将拥有一个定义公共接口并声明私有实现的头文件,以及一个或多个提供这些实现的 .cpp 文件。unique_ptr<T>只要公共接口类型的析构函数未在标头中定义,就可以在这些情况下工作:

///Interface.h
class Impl;

class Interface
{
private:
  unique_ptr<Impl> impl_;

public:
  ~Interface(); //Declaration only; prevent compiler-definition in header
};

///Interface.cpp
class Impl {...};

Interface::~Interface() = default;
Run Code Online (Sandbox Code Playgroud)

这可以防止包括 header 在内的人员实例化 的unique_ptr<Impl>析构函数。