我有一个指向指针列表的指针,作为私有变量。我还有一个 getter 返回指向列表的指针。我需要保护它免受更改。
我找不到如何使用reinterpret_cast或const_cast。
class typeA{
shared_ptr<list<shared_ptr<typeB>>> l;
public:
shared_ptr<list<shared_ptr<const typeB>>> getList(){return (l);};
};
编译器返回:
error: could not convert ‘((typeA*)this)->typeA::x’ from ‘std::shared_ptr<std::__cxx11::list<std::shared_ptr<typeB> > >’ to ‘std::shared_ptr<std::__cxx11::list<std::shared_ptr<const typeB> > >’|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|
看起来const shared_ptr<list<shared_ptr<typeB>>>
和shared_ptr<const list<shared_ptr<typeB>>>
工作正常。
是否可以将返回l
作为完全常量,例如:
const shared_ptr<const list<shared_ptr<const typeB>>>
或者至少喜欢:
shared_ptr<list<shared_ptr<const typeB>>>
?
引用而不是指针不是一种选择。将l
声明为shared_ptr<list<shared_ptr<const typeB>>>
也不是想要的解决方案。
编辑:不再有"int"。
似乎不可能完全满足我的需求,但建议的解决方案很好。是的,复制指针是可以接受的。
我的坏我没有立即输入B型。我知道引用相对于指针的一些优点,但我希望有一些类似的解决方案。
您可以从原始列表中创建一个新的const int
列表并返回:
std::shared_ptr<std::list<std::shared_ptr<const int>>> getList(){
return std::make_shared<std::list<std::shared_ptr<const int>>>(l->begin(), l->end());
}
如果要阻止用户对返回的列表进行更改,请使其也具有 const:
std::shared_ptr<const std::list<std::shared_ptr<const T>>> getList(){
return std::make_shared<const std::list<std::shared_ptr<const T>>>(l->cbegin(), l->cend());
}
此函数返回的共享指针不指向原始列表,而是指向新创建的列表。
另一种方法是提供迭代器,当取消引用时,返回const T&
(其中T是您实际存储的类型)。这样,就无需在每次要浏览列表时都复制整个列表。例:
#include <iostream>
#include <list>
#include <memory>
struct example {
int data;
example(int x) : data(x) {}
};
template <class T>
class typeA {
std::shared_ptr<std::list<std::shared_ptr<T>>> l = std::make_shared<std::list<std::shared_ptr<T>>>();
public:
template< class... Args >
void add( Args&&... args ) {
l->emplace_back(std::make_shared<T>(std::forward<Args>(args)...));
}
// a very basic iterator that can be extended as needed
struct const_iterator {
using uiterator = typename std::list<std::shared_ptr<T>>::const_iterator;
uiterator lit;
const_iterator(uiterator init) : lit(init) {}
const_iterator& operator++() { ++lit; return *this; }
const T& operator*() const { return *(*lit).get(); }
bool operator!=(const const_iterator& rhs) const { return lit != rhs.lit; }
};
const_iterator cbegin() const noexcept { return const_iterator(l->cbegin()); }
const_iterator cend() const noexcept { return const_iterator(l->cend()); }
auto begin() const noexcept { return cbegin(); }
auto end() const noexcept { return cend(); }
};
int main() {
typeA<example> apa;
apa.add(10);
apa.add(20);
apa.add(30);
for(auto& a : apa) {
// a.data = 5; // error: assignment of member ‘example::data’ in read-only object
std::cout << a.data << "n";
}
}
当您将指针到非常量转换为指向常量指针时,您有两个指针。此外,指向非常量的指针列表与指向常量的指针列表是完全不同的类型。
因此,如果要返回指向常量指针列表的指针,则必须具有指向常量指针的列表。但是你没有这样的清单。您有一个指向非常量的指针列表,这些列表类型不可相互转换。
当然,您可以将指向 non-const 的指针转换为指向 const 的指针列表,但您必须了解它是一个单独的列表。指向前一种类型的指针不能指向后者。
所以,这里有一个转换列表的例子(我没有测试,可能包含错别字或错误):
list<shared_ptr<const int>> const_copy_of_list;
std::transform(l->begin(), l->end(), std::back_inserter(const_copy_of_list),
[](auto& ptr) {
return static_pointer_cast<const int>(ptr);
});
// or more simply as shown by Ted:
list<shared_ptr<const int>> const_copy_of_list(l->begin(), l->end());
由于我们创建了一个全新的列表,l
无法指出,因此返回指针毫无意义。让我们返回列表本身。如果需要,调用方可以将列表包装在共享所有权中,但当它不符合他们的需求时,则不必这样做:
list<shared_ptr<const int>> getConstCopyList() {
// ... the transorm above
return const_copy_of_list;
}
请注意,虽然列表是分开的,但内部的指针仍指向相同的整数。
作为旁注,请考虑int
对象的共享所有权是否对您的程序有意义 - 我假设这是示例的简化。
还要重新考虑"引用而不是指针不是一种选择"是否是一个明智的要求。
你的问题直接在于
但我不想混合引用和指针。只有指针更容易、更干净。
你在这里发现的是这种说法是错误的。list<TypeB>
可以绑定const list<TypeB> &
引用,并且列表的任何成员都不允许对TypeB
对象进行任何修改。
class typeA {
std::vector<typeB> l;
public:
const std::vector<typeB> & getList() const { return l; };
};
如果你真的必须有const typeB
,你可以返回一个添加了const
的l
投影,但那将不是一个容器,而是一个范围(使用投票进入C++20的ranges
库,另请参阅其独立实现)
std::shared_ptr<const typeB> add_const(std::shared_ptr<typeB> ptr)
{
return { ptr, ptr.get() };
}
class typeA {
std::vector<std::shared_ptr<typeB>> l;
public:
auto getList() const { return l | std::ranges::transform(add_const); };
};
另一种选择是您可以将std::shared_ptr
包裹在类似std::experimental::propagate_const
的东西中,然后直接将它们退回。
你在这里有一个非常复杂的结构:
shared_ptr<list<shared_ptr<typeB>>> l;
这是三个间接级别,其中两个具有引用计数生存期管理,第三个是容器(而不是内存连续)。
当然,鉴于这种复杂的结构,将其转换为另一种类型并不容易:
shared_ptr<list<shared_ptr<const typeB>>>
请注意,根据标准库的设计,std::list<A>
和std::list<const A>
是两种不同的类型。当您想要将非修改句柄传递给容器时,通常应该使用const_iterator
s.
在您的情况下,list
顶部有一个shared_ptr
,因此如果您想要引用计数行为,则不能使用迭代器。
在这一点上,问题来了:你真的想要这种行为吗?
- 您是否预计会出现
typeA
实例被销毁,但仍有一些其他typeA
实例具有相同的容器的情况? - 您是否预计会出现这样一种情况:共享容器的所有
typeA
实例都被销毁,但在运行时的其他位置仍有对该容器的一些引用? - 您是否预计会出现容器本身被破坏的情况,但您仍然对某些元素有一些引用?
- 您是否有任何理由使用
std::list
而不是更传统的容器来存储共享指针?
如果你对所有要点的回答都是"是",那么为了实现你的目标,你可能必须设计一个新的类,它的行为可以作为你的shared_ptr<list<shared_ptr<typeB>>>
的持有者,同时只提供对元素的const
访问。
但是,如果在其中一个要点上,您的答案是否定的,请考虑重新设计l
类型。我建议从std::vector<typeB>
开始,然后只逐个添加必要的修改。
模板的问题在于,对于任何
template <typename T>
class C { };
任何两对C<TypeA>
和C<TypeB>
都是完全不相关的类——如果TypeA
和TypeB
只是在const
性上不同,情况也是如此。
因此,您真正想要拥有的东西在技术上是不可能的。我现在不会提出新的解决方法,因为已经有,但请尝试进一步查看:如评论中所述,您可能会面临 XY 问题。
问题是:用户会如何处理这样的列表?她/他可能正在迭代它 - 或访问单个元素。那为什么不让你的整个班级看起来像一个列表呢?
class typeA
{
// wondering pretty much why you need a shared pointer here at all!
// (instead of directly aggregating the list)
shared_ptr<list<shared_ptr<typeB>>> l;
public:
shared_ptr<list<shared_ptr<typeB>>>::const_iterator begin() { return l->begin(); }
shared_ptr<list<shared_ptr<typeB>>>::const_iterator end() { return l->end(); }
};
如果您使用向量而不是列表,我仍然会提供一个索引运算符:
shared_ptr<typeB /* const or not? */> operator[](size_t index);
现在有一个问题至今仍未解决:返回的两个const_iterators
有一个不可变的共享指针,但点仍然是可变的!
这有点麻烦 - 你现在需要实现自己的迭代器类:
class TypeA
{
public:
class iterator
{
std::list<std::shared_ptr<int>>::iterator i;
public:
// implementation as needed: operators, type traits, etc.
};
};
请查看std::iterator
的完整示例 - 但请注意,std::iterator
已被弃用,因此您需要自己实现类型特征。
如果您内部使用std::vector
,要使用的迭代器标记将是std::bidirectional_iterator_tag
或random_access_iterator_tag
(contiguous_iterator_tag
带有 C++20)。
现在重要的是如何实现所需的两个运算符:
std::shared_ptr<int const> TypeA::iterator::operator*()
{
return std::shared_ptr<int const>(*i);
}
std::shared_ptr<int const> TypeA::iterator::operator->()
{
return *this;
}
其他运算符只是将操作转发到内部迭代器(增量、递减(如果可用)、比较等)。
我并不是说这是圣杯,是你在任何情况下都需要遵循的道路。但这是一个有价值的选择,至少值得考虑......