公开类拥有的资源的正确方法是啥?

Posted

技术标签:

【中文标题】公开类拥有的资源的正确方法是啥?【英文标题】:What is the right way to expose resources owned by a class?公开类拥有的资源的正确方法是什么? 【发布时间】:2014-05-31 11:41:56 【问题描述】:

假设我有一个库,它有一个 Document 类。 Document 的一个实例可以拥有多个Field 实例。 Field 有多个子类(例如IntegerFieldStringField),甚至API 用户也可以对其进行子类化,并将子类实例提供给Document(假设用户可以开发自定义类型的数据来存储在字段中)。

我想通过 API 公开Document 拥有的Field 实例,以便用户可以与它们交互,但不转移所有权

这样做的正确方法是什么?

我想过:

暴露 const std::unique_ptr<Field>& 引用 - 这感觉很丑 暴露一个普通的 Field* 指针 - 这感觉不对,因为用户可能不确定是否应该删除该实例 改用std::shared_ptr - 这感觉很糟糕,因为所有权并未真正共享

例如,

class Document 
private:
    std::map<std::string, std::unique_ptr<Field> > fields;
    // ...
public:
    // ...

    // How is this done properly?
    const std::unique_ptr<Field> &field(const std::string &name) 
        return fields[name];
    

期待您的建议。 (我也欢迎有关 @Fulvio 建议的替代方法的建议。)

【问题讨论】:

为什么不简单地Bar&amp; 并抛出缺少的名字? 您忘记了一个替代方案:将所有权归还给调用者的getBar,以及将所有权归还给FooreleaseBar。它要求您的 Foo 类的用户在完成后始终释放其所有权。 @Joacim - 我没有忘记,但我在问题中说我想在不转移所有权的情况下这样做。 :) 【参考方案1】:

我通常返回对数据的引用而不是对unique_ptr的引用:

const Bar &getBar(std::string name) const 
    return *bars[name];

如果您希望能够返回空项目,您可以返回原始指针(如果为空,则返回 nullptr)。或者更好的是你可以使用boost::optional(C++14 中的std::optional)。

如果引用的存在时间可能比所有者长(多线程环境),我在内部使用 shared_ptr 并在访问方法中返回 weak_ptr

【讨论】:

你是对的。它从规范草案中被否决。但它被移到了单独的技术规范中。 我希望它尽快可用。它可以提高代码质量的用例太多了! @Danvil :请参阅github.com/akrzemi1/Optional 了解工作实现。 在这种情况下,我看不到 optional 相对于原始指针的任何好处。 observing 原始指针基本上可以实现相同的目标。【参考方案2】:

如果可能的话,我会尽量避免暴露Document 的内部结构,但如果失败,我将返回const Field&amp;,或者如果您需要它可以为空,则返回const Field*。两者都清楚地表明Document 保留所有权。另外,将方法本身设为const

您可以使用返回 Field&amp;Field* 的方法的非 const 版本,但如果可以的话,我会避免这样做。

【讨论】:

【参考方案3】:

这还没有考虑,我猜:返回一个具有相同接口的代理对象。

代理对象的持有者拥有该代理对象的所有权。 代理持有对被引用对象的任何引用(可能很弱)。 删除对象后,您可能会引发一些错误。 代理对象没有所有权。

也许已经提到过。我对 C++ 不是很熟悉。

【讨论】:

总的来说这是一个非常好的主意,但恐怕更适合动态类型的语言【参考方案4】:

我通常不喜欢将参考资料交给内部成员。如果另一个线程修改它会发生什么?相反,如果我不能分发副本,我更喜欢更实用的方法。像这样。

class Foo 
private:
    std::map<std::string, std::unique_ptr<Bar> > bars;

    // ...

public:

    // ...
    template<typename Callable> void withBar(const std::string& name, Callable call) const 
        //maybe a lock_guard here?
        auto iter = bars.find(name);
        if(iter != std::end(bars)) 
            call(iter->second.get());
        
    

这样,拥有的内部永远不会“离开”拥有它的类,并且所有者可以控制不变量。如果请求的条目不存在,也可以有一些细节,比如使代码成为 noop。可以这样用,

myfoo.withBar("test", [] (const Bar* bar)
   bar->dostuff();
);

【讨论】:

好吧,可以将 ref 保存在他的 Callable 中,不是吗?【参考方案5】:

正如其他人从技术角度回答的那样,我想向您指出一种不同的方法并修改您的设计。这个想法是尽量尊重the Law of Demeter,并且不授予对对象子组件的访问权限。这有点难做,没有具体的例子,我无法提供很多细节,但试着想象一个由 Pages 组成的类 Book。如果我想用您当前的设计打印一页、两页或更多页的书,我可以这样做:

auto range = ...;
for( auto p : book.pages(range) )
  
  p->print();
  

在遵守得墨忒耳的同时,你将拥有

auto range = ...;
book.print( /* possibly a range here */ );

这倾向于更好的封装,因为您不依赖 book 类的内部细节,并且如果其内部结构发生变化,您无需对客户端代码执行任何操作。

【讨论】:

这是一个好主意,但是(考虑到您的示例)如果Page 类可以有多个子代并且也可以由用户提供怎么办?在这种情况下,并非所有可能的方法在设计时都是已知的,因此暴露对象本身可能是可行的。 设计总是高度上下文相关的,所以你做出的每一个选择可能会也可能不会满足你的要求。我的意图是指出一种设计替代方案,但正如我所说,在不知道您的要求/约束​​/上下文的情况下,这意味着建议在其他地方寻找可能的替代解决方案。无论如何,如果您想使用仅在子类中可用的方法,您正在将您的客户端代码调整为绕过多态性的特定类型。再说一次,很难用 Foo 和 Bar 来推理,抱歉 :) 我很欣赏你的想法并更新了我的问题以更好地反映我实际在做什么。我期待着你的想法:) 主要依赖Field接口。如果客户端只能使用接口级别的方法,那么您可以再次为您的文档提供一堆方法,这些方法可以将调用转发(我很简单)内部字段。如果您的客户派生类添加方法,那么您只能公开字段,并且根据您的 Document 定义,您不能使用引用但需要一个指针:return fields[name].get();此外,您的返回类型是一个普通的 Field* 您如何看待其他人建议的引用或weak_ptr【参考方案6】:

我会返回 std::weak_ptr&lt; Bar &gt;(如果 C++11)或 boost::weak_ptr(如果不是 C++11)。这清楚地表明这是堆内存,并且不会冒对不存在内存的引用的风险(就像Bar &amp; 一样)。它还明确了所有权。

【讨论】:

我认为 weak_ptr 承诺转换为 shared_ptr 这不在问题的范围内。 我不认为它确实明确了所有权。它说,“如果你想分享这个”。很容易意外地与另一个Document 共享Field,然后您可能对一个Document 进行更改而影响另一个。【参考方案7】:

我会返回Bar&amp;(可能带有const)。

用户需要了解,如果从地图中删除元素,则引用将失效 - 但由于单一所有权模型,无论您做什么,都会出现这种情况。

【讨论】:

@MadScienceDreams:weak_ptr 没有给出 OP 想要的单一所有权模型:任何人都可以使用弱指针来共享所有权。它还增加了一些开销,只有当用户对返回的引用/指针做了一些奇怪的事情时才需要这些开销。但你是对的,在类似情况下它可能是一个合适的选择。 对不起,由于某种原因,我认为您可以将非所有权指针指向 unique_ptrs,我的错:-P (来自我的旧评论)这还具有公开 C++98 兼容接口的好处,该接口(如果您使用 impl idiom)允许您与非 C++11 代码进行交互.

以上是关于公开类拥有的资源的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

下载 WCF 服务公开的所有 WSDL 文件的最佳方法是啥?

java公开课-03-内部类

在反应应用程序中保护 Firebase API 密钥以使其无法公开访问的最佳方法是啥? [复制]

多个端点以公开同一资源的不同视图

由于 Databricks 不公开支持 spark-redshift lib,使用 Scala spark 从 Redshift 读取/写入 Redshift 的最佳方法是啥

Object类与常用类,内部类