【问题标题】:How to avoid shared_ptr overhead when doing PIMPL执行 PIMPL 时如何避免 shared_ptr 开销
【发布时间】:2022-01-09 02:04:39
【问题描述】:

AFAIK unique_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>();
} 

注释:

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

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

【问题讨论】:

  • AFAIK unique_ptr is quite tricky to use with PIMPL 如何定义棘手?只需在头文件中声明需要知道完整类型的析构函数和方法,并在定义不完整类型的地方使用= default(C++ Pimpl Idiom Incomplete Type using std::unique_ptr)进行定义
  • 对于这个问题的棘手定义:我不能只包含 fwd 声明头并编写“正常”(除了我必须使用指针,而不是值)代码,我需要制作一个助手构建自己。
  • 我不完全理解这个问题或你对(beside the fact I must use pointers, not values) code, I need to make a helper struct myself. 的意思。以下代码有什么问题? /*header*/struct Type { Type(); ~Type(); struct TypeImpl; std::unique_ptr&lt;TypeImpl&gt; impl }; /*source*/ struct TypeImpl{}; Type::Type() : impl(new Type::TypeImpl()) {} Type::~Type() = default;
  • @t.niese 注意,如果您希望它是可移动的,我相信您还必须明确添加移动操作符,因为用户定义的析构函数仍然禁止移动操作。但是我之前已经多次按照您的建议进行过,并且确实有效。
  • @NoSenseEtAl 我关心的是更多的可维护性和错误预防。使用std::shared_ptr,您需要确保使用 PImpl 的类确保正确完成复制和分配,并且不会发生意外引入两个对象共享同一个 impl 对象的可能性。对于std::unique_ptr,这是由类型给出的,如果你做错了什么,你会得到一个编译器错误。它肯定不会阻止你所有的错误,但可以防止你形成常见的错误。

标签: c++ shared-ptr c++20 pimpl-idiom


【解决方案1】:

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

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

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

///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;

这可以防止包括标头在内的人实例化unique_ptr&lt;Impl&gt; 的析构函数。

【讨论】:

  • 这是正确的方法(当然,过去对我来说效果很好)。唯一的评论可能是在Interface 中私下嵌套Impl 类。这清楚地表明,用户不应尝试创建 Impl 的独立实例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-19
  • 2018-04-20
  • 1970-01-01
  • 1970-01-01
  • 2012-12-25
相关资源
最近更新 更多