公开类所拥有资源的正确方法是什么?

时间:2014-04-16 14:21:11

标签: c++ c++11 unique-ptr ownership

我们说我有一个Document类的图书馆。 Document的实例可以拥有Field的多个实例。 Field有多个子类(例如IntegerFieldStringField),甚至API用户也可以将其子类化并将子类实例提供给Document(让我们说允许用户开发自定义类型的数据以存储在字段中。)

我希望通过API公开Field所拥有的Document个实例,以便用户可以与他们互动,但不转让所有权

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

我想到了:

  • 公开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建议的替代方法的建议。)

7 个答案:

答案 0 :(得分:13)

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

如果元素已从地图中删除,用户将需要了解该引用将失效 - 但由于单一所有权模型,无论您采取何种情况都是如此。

答案 1 :(得分:6)

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

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

在遵守Demeter的同时,你将拥有

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

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

答案 2 :(得分:4)

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

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

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

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

答案 3 :(得分:1)

我会返回std::weak_ptr< Bar >(如果是C ++ 11)或boost::weak_ptr(如果不是C ++ 11)。这使得它明确表明这是堆内存,并且不存在悬挂对不存在的内存的引用的风险(如Bar &那样)。它还使所有权明确。

答案 4 :(得分:1)

我一般不喜欢将内容提交给内部成员。如果另一个线程修改它会发生什么?相反,如果我不能分发副本,我更喜欢更实用的方法。这样的事情。

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

这种拥有的内部永远不会离开&#34;离开&#34;拥有它的类,所有者可以控制不变量。如果请求的条目不存在,也可以使代码成为noop。可以使用它,

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

答案 5 :(得分:1)

如果可能的话,我会尽量避免暴露Document的内部,但如果没有,我会返回const Field&或者如果你需要它可以为const Field*。两者都清楚地表明Document保留了所有权。另外,请将方法设为const

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

答案 6 :(得分:0)

我还在考虑这个问题:我想:返回一个具有相同接口的代理对象。

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

也许已经提到了。我对C ++并不熟悉。