我们说我有一个Document
类的图书馆。 Document
的实例可以拥有Field
的多个实例。 Field
有多个子类(例如IntegerField
和StringField
),甚至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建议的替代方法的建议。)
答案 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 ++并不熟悉。