c++ - 有没有一种方法可以针对永久对象优化shared_ptr?

标签 c++ optimization shared-ptr

我有一些使用shared_ptr的代码作为在我的应用程序中引用特定类型对象(称为T)的标准方法。为了提高效率,我尝试谨慎使用make_sharedstd::moveconst T&。但是,我的代码花了很多时间传递shared_ptr s(我包裹在shared_ptr中的对象是整个caboodle的中心对象)。更重要的是,shared_ptr通常指向一个对象,该对象用作“无值”的标记;该对象是特定T子类的全局实例,并且由于其refcount永远不会为零,因此它可以永久存在。

使用“无值”对象很不错,因为它以良好的方式响应发送到这些对象的各种方法,并表现出我希望“无值”的行为。但是,性能指标表明,我的代码中花费了大量时间来增加和减少该全局单例对象的refcount,使新的shared_ptr引用该对象,然后销毁它们。举个例子:对于一个简单的测试用例,如果我将nullptr粘贴在shared_ptr中以指示“无值”,而不是使它们指向全局单例T“无值”对象,则执行时间从9.33秒减少到7.35秒。这是非常重要的区别。如果运行在更大的问题上,该代码将很快用于在计算集群上进行多日运行。所以我真的需要加快速度。但是我也很想拥有我的“无值(value)”对象,这样我就不必在我的所有代码中都检查nullptr,对此情况进行了特殊的处理。

所以。有没有办法吃我的蛋糕呢?特别是,我在想我可以通过某种方式将shared_ptr子类化为一个可以与“无值”对象一起使用的“shared_immortal_ptr”类。子类的行为就像普通的shared_ptr一样,但是它根本不会增加或减少其引用计数,并且会跳过所有相关的簿记。这样的事情可能吗?

我也在考虑制作一个内联函数,该函数可以在get()上执行shared_ptr,并且如果get()返回nullptr,则可以替换指向单例不朽对象的指针;我想,如果我在代码中的任何地方都使用了它,而从未在*上直接使用->shared_ptr,那我会被隔离。

还是针对这种情况,我还没有遇到另一个好的解决方案?

最佳答案

Galik询问了有关您的遏制策略的中心问题。我假设您已经考虑到这一点,并且有理由依赖shared_ptr作为一种公共(public)遏制​​策略,没有其他选择。

我不得不提出似乎有争议的建议。您所定义的是,您需要一种类型的shared_ptr,它永远不会有nullptr,但是std::shared_ptr不会这样做,并且我检查了STL的各种版本,以确认提供的客户删除器不是解决方案的入口点。

因此,可以考虑制作自己的智能指针,也可以采用根据需要进行更改的指针。基本思想是建立一种shared_ptr,可以指示该shadow_pointer将其影子指针指向它不拥有的全局对象。

您有std::shared_ptr的源代码。该代码不便于阅读。可能很难使用。这是一种途径,但是您当然可以复制源代码,更改 namespace 并实现所需的行为。

但是,在90年代中期将模板首次引入时代的编译器时,我们所有人要做的第一件事就是开始构建容器和智能指针。智能指针非常容易编写。它们很难设计(或曾经设计过),但是随后您有一个要建模的设计(您已经使用过)。

您可以实现shared_ptr的基本接口(interface)来创建替换对象。如果您很好地使用了typedef,则应该只在少数几个地方进行更改,但是至少搜索和替换将相当不错。

这是我建议的两种方法,两种方法都具有相同的功能。从库中采用shared_ptr,或者从头开始制作一个。您会惊讶地如此迅速地进行替换。

如果您采用std::shared_ptr,则主要主题是了解shared_ptr如何确定应减少的值。在大多数实现中,shared_ptr必须引用一个节点,在我的版本中,它称为控制块(_Ref)。当引用计数达到零时,节点拥有要删除的对象,但是如果_Ref为null,则shared_ptr自然跳过该对象。但是,->和*之类的运算符或get函数不会费心检查_Ref,它们只是返回阴影或_Ptr(在我的版本中)。

现在,当调用重置时,_Ptr将设置为nullptr(或在我的源代码中为0)。分配给另一个对象或指针时将调用Reset,因此,即使使用对nullptr的分配,此操作也有效。关键是,对于您需要的这种新型的shared_ptr,您可以简单地更改行为,以便每当发生这种情况(重置为nullptr)时,都将_Ptr(shared_ptr中的阴影指针)设置为“无全局值”对象地址。

*,get或->的所有用法都将返回该无值对象的_Ptr,并且在另一个赋值中使用时将正确运行,或者再次调用reset,因为这些函数不依赖于影子指针来作用于节点,并且由于在这种特殊情况下该节点(或控制块)将为nullptr,因此shared_ptr的其余部分将表现为好像正确指向了nullptr一样-即,不删除全局对象。

显然,将std::pointer更改为此类特定于应用程序的行为听起来很疯狂,但是坦率地说,这是性能工作使我们难以做的事情。否则会发生奇怪的事情,例如为了获得更高的C原始速度而偶尔放弃C++或汇编程序。

修改std::shared_ptr源(作为副本用于此特殊目的)不是我要选择的(事实上,我已经遇到了您的情况的其他版本,因此数十年来我已经多次做出了选择)。

为此,我建议您构建一个基于策略的智能指针。我觉得很奇怪,我今天早些时候(或昨天,凌晨1:40)在另一篇文章中提出了这个建议。

我指的是Alexandrescu于2001年写的书(我认为这是Modern C++,有些单词我不记得了)。他介绍了loki,其中包括基于策略的智能指针设计,该设计仍在他的网站上发布并免费提供。

我认为,这个想法应该已经合并到shared_ptr中。

基于策略的设计被实现为模板类的范例,该模板类是从一个或多个参数中派生的,如下所示:

template< typename T, typename B >
class TopClass : public B {};

这样,您可以提供B,从中构建对象。现在,B可能具有相同的构造,也可能是从其第二个参数(或多个派生,但是设计可行)得出的策略级别。

可以组合各层以实现各种类别中的独特行为。

例如:
std::shared_ptrstd::weak_ptr是单独的类,它们作为一个家族与其他家族(节点或控制块)进行交互,以提供智能指针服务。但是,在我多次使用的设计中,这两个是由同一顶级模板类构建的。在该设计中,shared_ptr和weak_ptr之间的区别在于模板的第二个参数中提供的附件策略。如果使用弱连接策略作为第二个参数实例化该类型,则它是一个弱指针。如果给出了强有力的附件策略,那将是明智的选择。

一旦创建了策略设计模板,就可以引入原始设计中没有的层(扩展它),或者“拦截”行为并将其像您当前所需的那样专门化-而不会破坏原始代码或设计。

我开发的智能指针库具有很高的性能要求,还有许多其他选项,包括自定义内存分配和自动锁定服务,这些特性使对智能指针线程的写入变得安全(std::shared_ptr不提供)。接口(interface)和许多代码是共享的,但是可以通过选择不同的策略来简单地构建几种不同类型的智能指针。要更改行为,可以在不更改现有代码的情况下插入新策略。目前,我同时使用了std::shared_ptr(几年前在boost时使用的)和我几年前开发的MetaPtr库,后者在我需要高性能或灵活选项(如您的库)时使用。

如loki所示,如果std::shared_ptr是基于策略的设计,则可以使用shared_ptr进行此操作,而不必复制源并将其移动到新的命名空间。

无论如何,只要创建一个共享指针即可将影子指针指向重置为nullptr的全局对象,而使节点指向null则提供了您描述的行为。

关于c++ - 有没有一种方法可以针对永久对象优化shared_ptr?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32774424/

相关文章:

algorithm - 在模拟中优化决策的最佳算法

c++ - 成员函数返回错误的智能指针

c++ - 运行时检查失败 #2 - bMatix 周围的堆栈已损坏

c++ - 初学者 - 我的 "produce word backwards"程序有什么问题?

c++ - 如何监控包含所有子文件夹和文件的文件夹?

c++ - 编译器是否优化 C++ 中静态值的 if 语句

c++ - 我如何分析超出每个功能级别的代码?

c++ - boost::program_options 缓冲区溢出

c++ - std::shared_ptr 控制 block 中的虚函数

c++ - 使用 std::vector<boost::shared_ptr<Base_Class>> 或 boost::ptr_vector<Base> 的性能考虑因素是什么?