我应该使用哪个智能指针

Which smart pointer should I use?

本文关键字:智能 指针 我应该      更新时间:2023-10-16

我正在研究一个由许多不同类型(Properties、Parent、Child等)组成的模型。每种类型都与c api中的一组函数相关联。例如:

Type "Properties":
  char* getName(PropertiesHandle);
  char* getDescription(PropertiesHandle);
Type "Parent"
  PropertiesHandle getProperties(ParentHandle);
  ChildHanlde getFirstChild(ParentHandle);
Type "Child"
  PropertiesHandle getProperties(ChildHandle);
  ParentHanlde getParent(ChildHandle);
  ChildHandle getNextChild(ChildHandle);

我为这个C api库创建了一组C++接口,如下所示:

class IProperties
{
public:
  virtual std::string getName() = 0;
  virtual std::string getDescription() = 0;
};
class IParent
{
public:
  virtual std::shared_ptr<IProperties> getProperties() = 0;
  virtual std::shared_ptr<IChild> getFirstChild() = 0;
};
class IChild
{
public:
  virtual std::shared_ptr<IProperties> getProperties() = 0;
  virtual std::shared_ptr<IParent> getParent() = 0;
  virtual std::shared_ptr<IChild> getNextChild() = 0;
};

然后,我通过类Properties、Parent和Child实现这些接口。

因此,Child实例将通过其构造函数获取其特定的ChildHandle,其getParent函数将如下所示:

std::shared_ptr<IParent> getParent()
{
    // get the parent handle and wrap it in a Parent object
    return std::shared_ptr<IParent>(new Parent(_c_api->getParent(_handle)));
}

在你看来,我在这里返回shared_ptr合理吗。我不能使用std::unique_ptr,因为Google Mock要求模拟方法的参数和返回值是可复制的。我通过Google Mock在测试中嘲笑这些界面。

我也在思考未来如何优化,这可能会带来循环引用的可能性。如果使用缓存来避免对C api的多次调用(例如,子级不需要多次建立其父级),并使用child构造函数获取其父级,则可能会导致这种情况。这将需要使用weak_ptrs,这将更改接口和我的许多代码。。。

关键问题是:返回指针的语义是什么?

  • 如果返回的父/子/属性对象的生存期与返回的(可能在某种意义上是拥有的)对象无关,则返回shared_ptr是合理的:这表明调用者和被调用者具有决定对象生存期的平等权利

    std::shared_ptr<IChild> child = parent->getFirstChild();
    // now I can keep child around ... if parent is destroyed, one
    // orphaned subtree is magically kept around. Is this desirable?
    
  • 如果返回的对象的生存期依赖于被调用者自己的生存期,则:

    • shared_ptr将错误地暗示调用者将返回对象的生存期延长到被调用者的生存期之外是有意义的
    • unique_ptr会错误地建议转让所有权
    • raw指针没有明确地做出任何误导性的承诺,但也没有给出任何关于正确使用的提示

因此,如果调用者只是获得对对象内部状态的工作引用,而没有转移所有权或延长对象生存期,则不建议使用任何智能指针。

考虑只返回一个引用。

返回shared_ptr没有错,但我会努力让您相信这可能不是最好的选择。

通过使用智能指针,您获得了安全性的优势,但API的用户失去了使用最适合其需求的智能指针类型的灵活性,而不得不始终使用shared_ptr

这也取决于你有多重视安全而不是灵活性,但我个人会考虑返回一个裸指针,并允许用户使用他想要的智能指针。当然,如果出于某种原因需要我使用shared_ptr,我会的。

shared_ptr很好,但它确实为最终用户提供了一些限制,例如C++11支持。原始指针,或者允许他们定制智能指针的特性,可以为最终用户提供更大的灵活性。

不管指针是什么,我建议小心实现引入的语义。在当前的实现中,每个访问器调用都实例化一个新的包装,等价性检查将失败。考虑以下代码:

auto child = parent->getFirstChild();
if ( parent == child->getParent() ) // Will be false, as they point to different
                                    // instantiations of Parent.
  ...
if ( child->getParent() == child->getParent() ) // False for the same reason.
  ...
auto sibling = child->getNextChild();
if ( parent == sibling->getParent() ) // Also false for the same reason.
  ... 

此外,当使用std::shared_ptr时,可以考虑使用std::make_shared来减少分配时出现的一些开销。