使用std::vector< std::shared_ptr<const T> >是一种反模式吗?

25

长时间以来,我一直在同时使用std::vectorstd::shared_ptr。最近,每当需要指向一个const对象的指针时,我开始使用std::shared_ptr<const T>。这样做是可以的,因为std::shared_ptr<T>可以转换为std::shared_ptr<const T>,然后它们共享相同的引用计数器,感觉很自然。

但当我尝试使用std::vector< std::shared_ptr<const T> >等结构时,就会遇到问题。为了简化,我将这两个结构表示为:

template <class T>
using SharedPtrVector = std::vector< std::shared_ptr<T> >;

template <class T>
using SharedConstPtrVector = std::vector< std::shared_ptr<const T> >;

问题在于,尽管SharedPtrVectorSharedConstPtrVector非常相似,但SharedConstPtrVector无法转换为SharedPtrVector

因此,每次我想要正确使用const并编写像这样的函数:

void f(const SharedConstPtrVector<T>& vec);

我无法将const SharedPtrVector<T>传递给f,没有任何办法。

我考虑过很多并且考虑了几种替代方案:

  1. 编写转换函数

    template <typename T>
    SharedConstPtrVector<T> toConst(const SharedPtrVector<T>&);
    
  2. 以通用形式编写代码:

    template <typename T>
    void f(const std::vector< std::shared_ptr<T> >& vec);
    
    或者
    template <typename TIterator>
    void f(TIterator begin, TIterator end);
    
  3. 放弃使用std::vector< std::shared_ptr<const T> >的想法。

1.的问题是计算负担过重,代码变得更加丑陋。而2.则给代码带来了“一切都是模板”的风格。

我是一名经验不足的程序员,不想走错方向。我想听听有经验的人对这个问题的建议。


2
取决于使用情况,与模式/反模式一样 => 金鎚 - πάντα ῥεῖ
1
我觉得你的问题在于当你不需要修改容器时,传递了一个容器。这就是范围(和迭代器对)存在的原因。您也不需要使用模板,您可以像使用迭代器一样在此处使用指针。另一方面,如果 f 是一个算法,请将其变为模板使其通用=>可重复使用(作为数学家,您可能会喜欢 Alexander Stepanov 的方法)。 - dyp
@dyp дҪ д№ҹеҸҜд»ҘеңЁиҝҷйҮҢдҪҝз”ЁжҢҮй’ҲдҪңдёәиҝӯд»ЈеҷЁпјҢ然еҗҺдҪҝз”Ёstd::vector::dataжҲҗе‘ҳеҮҪж•°дј йҖ’еҗ‘йҮҸпјҹжҲ–иҖ…дҪ жҳҜжҖҺд№Ҳжғізҡ„пјҹ - Martin Drozdik
我根本不会放弃这个想法。通过迭代器转向基于范围的算法大多是有意义的。然而,它的缺点是没有提供执行容器操作(添加/删除元素)的手段,但如果容器引用是非const的话,您可以使用尝试的方法来执行此操作。如果引用是const的,我认为使用基于范围的替代方案是没有理由使用的。 - WhozCraig
1
@MartinDrozdik 没错,vec.data()vec.data() + vec.size() - dyp
很长一段时间我一直在同时使用std::vectorstd::shared_ptr,但这听起来好像我在误用它们。shared_ptr不应该经常出现。 - user541686
5个回答

22

建议您重新审查设计,以便确定这些对象的明确所有者。缺乏明确的所有权会导致人们使用共享智能指针。

Bjarne Stroustrup建议只在最后一种情况下使用智能指针。他的建议(从好到坏)是:

  1. 按值存储对象。
  2. 按值将多个对象存储在容器中。
  3. 如果没有其他办法,请使用智能指针。

请参见Bjarne Stroustrup - C++的本质:附有C++84、C++98、C++11和C++14示例,时间为0:37:40。


我使用shared_ptr来管理数据,这些数据被同时运行的多个GUI窗口所访问。我认为这是一个很好的使用案例,但也许这是我过早进行的优化。我将尝试采用“按值传递”的方法。 - Martin Drozdik
2
@MartinDrozdik 可能是你的应用程序中的某个东西(对象)拥有这些窗口。那个东西也可以拥有那些被这些窗口共享的对象。在应用程序中通常存在对象层次结构,如果它被隐藏了,请努力揭示它。 - Maxim Egorushkin
1
@MartinDrozdik 你可以尝试使用MVC模式,让需要访问的窗口注册为观察者,并与拥有状态的另一个类建立联系。这样,你就可以清晰地识别所有权并制定访问规则。 - stonemetal
我们还需要记住,无论人们多么聪明,他们仍然是有缺陷的人类,有可能持有错误的观点。Bjarne 是个很酷的家伙,但并不是他说的每件事都是正确的。我也听过他声称使用 C++ 的绝大多数人都在错误地使用它。因此,如果我们毫无疑问地按照他的建议改变我们的思考和工作方式,那么我们应该卸载编译器并回家。 :) - user562566
@TechnikEmpire 振作起来,勇往直前。 - Maxim Egorushkin
显示剩余2条评论

8

1. 这个问题与shared_ptr<>无关,但普通指针已经出现了这个问题:

template<typename T>
void foo(std::vector<const T*>);

int a,b;
std::vector<int*> bar={&a,&b};
foo<???>(bar);        // no value for ??? works
< p >2 构造 vector<shared_ptr<T>> 只有在没有所有者持有对象时才是合理的。


3
甚至可以将其简化为 int const **int ** 的区别。后者无法转换为前者。 - dyp

2

这是一种有效的存储不可变(因此是线程安全的)对象的方法,是每个元素的写时复制缓存的构建块。绝对不是反模式。


1
如果您坚持使用std::vector,您可以尝试将其封装到句柄-体惯用结构中。
这样可以在const句柄中保持非const共享指针。

0

如果我要存储数千个3D点(例如在一个std::vector中),并且想将这些条目的地址推入另一个结构(比如用于排序或其他目的的堆),而且这个辅助结构可能需要删除其中一些条目(例如传感器所述准确度范围内的值),那么谁是“所有者”?看起来最好的解决方案是使用智能指针的向量,正如Stroustrup建议的应该是最后的选择,而这些点本身分配在堆上。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接