如何 return 一个指向指针列表的私有指针作为 const?
How to return a private pointer to a list of pointers as const?
我有一个指向指针列表的指针,作为私有变量。我还有一个 getter,return 是指向列表的指针。我需要保护它免受更改。
我找不到如何在此使用 reinterpret_cast 或 const_cast。
class typeA{
shared_ptr<list<shared_ptr<typeB>>> l;
public:
shared_ptr<list<shared_ptr<const typeB>>> getList(){return (l);};
};
编译器returns:
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>>>
工作正常。
是否可以将 return l
作为一个完整的 const,例如:
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'。
似乎不可能完全符合我的要求,但建议的解决方案很好。是的,复制指针是可以接受的。
糟糕,我没有立即输入 typeB。我知道引用比指针有一些优势,但我希望有一些类似的解决方案。
当您将一个指向非常量的指针转换为一个指向常量的指针时,您有两个指针。此外,指向非常量的指针列表与指向常量的指针列表是完全不同的类型。
因此,如果您想 return 一个指向常量指针列表的指针,您必须拥有一个常量指针列表。但是你没有这样的清单。您有一个指向非常量的指针列表,并且这些列表类型不可相互转换。
当然,您可以将指向非常量的指针转换为指向常量的指针列表,但您必须了解它是一个单独的列表。指向前一种类型的指针不能指向后者。
所以,这里是一个转换列表的例子(我没有测试,可能包含错别字或错误):
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
无法指向它,因此 return 一个指针没有什么意义。让我们 return 列表本身。如果需要,调用者可以将列表包装在共享所有权中,但在违反他们的需要时不必这样做:
list<shared_ptr<const int>> getConstCopyList() {
// ... the transorm above
return const_copy_of_list;
}
请注意,虽然列表是分开的,但其中的指针仍指向相同的整数。
作为旁注,请考虑 int
对象的共享所有权是否对您的程序有意义 - 我假设它是对示例的简化。
还要重新考虑 "References instead of pointers is not an option" 是否是一个合理的要求。
您可以从原始列表中创建 const int
的新列表,并且 return:
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());
}
如果您想阻止人们更改 returned 列表,请将其也设置为常量:
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());
}
此函数return生成的共享指针不指向原始列表,而是指向新创建的列表。
另一种方法是提供迭代器,当取消引用时,returns 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";
}
}
你的问题就在
but I do not want to mix references and pointers. It is easier and cleaner to have just pointers.
您在这里发现的是该陈述错误。 list<TypeB>
可以绑定 const list<TypeB> &
引用,列表成员中的 none 将允许对 TypeB
对象进行任何修改。
class typeA {
std::vector<typeB> l;
public:
const std::vector<typeB> & getList() const { return l; };
};
如果你 真的 必须有 const typeB
,你可以 return projection l
添加了 const
,但那不会是 Container, but instead a Range (using the ranges
library voted into C++20, see also its standalone implementation)
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
之类的东西中,然后直接 return 它们。
模板的问题是对于任何
template <typename T>
class C { };
任何两对 C<TypeA>
和 C<TypeB>
完全不相关 classes – 如果 TypeA
和 TypeB
仅在 const
-ness.
所以你真正想要的东西在技术上是不可能的。我现在不会提出新的解决方法,因为 已经 ,但试着看得更远一点:正如评论中已经指出的那样,你 可能 面临 XY 问题。
问题是:用户会用这样的列表做什么? She/he 可能正在对其进行迭代 - 或者访问单个元素。那为什么不把你的整个 class look/behave 变成一个列表呢?
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:
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
with 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;
}
其他运算符只会将操作转发给内部迭代器(递增、递减(如果可用)、比较等)。
我并不是说这是圣杯,是你在任何情况下都需要遵循的道路。但它是一个有价值的替代品,至少值得考虑...
你这里有一个非常复杂的结构:
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
而不是更传统的容器来存储共享指针吗?
如果您对所有要点的回答都是肯定的,那么为了实现您的目标,您可能需要设计一个新的 class 来充当您的 shared_ptr<list<shared_ptr<typeB>>>
的支架,而仅提供对元素的 const
访问。
但是,如果您对其中一个要点的回答是否定的,请考虑重新设计 l
类型。我建议从 std::vector<typeB>
开始,然后只一项一项地添加必要的修改。
我有一个指向指针列表的指针,作为私有变量。我还有一个 getter,return 是指向列表的指针。我需要保护它免受更改。
我找不到如何在此使用 reinterpret_cast 或 const_cast。
class typeA{
shared_ptr<list<shared_ptr<typeB>>> l;
public:
shared_ptr<list<shared_ptr<const typeB>>> getList(){return (l);};
};
编译器returns:
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>>>
工作正常。
是否可以将 return l
作为一个完整的 const,例如:
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'。
似乎不可能完全符合我的要求,但建议的解决方案很好。是的,复制指针是可以接受的。
糟糕,我没有立即输入 typeB。我知道引用比指针有一些优势,但我希望有一些类似的解决方案。
当您将一个指向非常量的指针转换为一个指向常量的指针时,您有两个指针。此外,指向非常量的指针列表与指向常量的指针列表是完全不同的类型。
因此,如果您想 return 一个指向常量指针列表的指针,您必须拥有一个常量指针列表。但是你没有这样的清单。您有一个指向非常量的指针列表,并且这些列表类型不可相互转换。
当然,您可以将指向非常量的指针转换为指向常量的指针列表,但您必须了解它是一个单独的列表。指向前一种类型的指针不能指向后者。
所以,这里是一个转换列表的例子(我没有测试,可能包含错别字或错误):
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
无法指向它,因此 return 一个指针没有什么意义。让我们 return 列表本身。如果需要,调用者可以将列表包装在共享所有权中,但在违反他们的需要时不必这样做:
list<shared_ptr<const int>> getConstCopyList() {
// ... the transorm above
return const_copy_of_list;
}
请注意,虽然列表是分开的,但其中的指针仍指向相同的整数。
作为旁注,请考虑 int
对象的共享所有权是否对您的程序有意义 - 我假设它是对示例的简化。
还要重新考虑 "References instead of pointers is not an option" 是否是一个合理的要求。
您可以从原始列表中创建 const int
的新列表,并且 return:
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());
}
如果您想阻止人们更改 returned 列表,请将其也设置为常量:
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());
}
此函数return生成的共享指针不指向原始列表,而是指向新创建的列表。
另一种方法是提供迭代器,当取消引用时,returns 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";
}
}
你的问题就在
but I do not want to mix references and pointers. It is easier and cleaner to have just pointers.
您在这里发现的是该陈述错误。 list<TypeB>
可以绑定 const list<TypeB> &
引用,列表成员中的 none 将允许对 TypeB
对象进行任何修改。
class typeA {
std::vector<typeB> l;
public:
const std::vector<typeB> & getList() const { return l; };
};
如果你 真的 必须有 const typeB
,你可以 return projection l
添加了 const
,但那不会是 Container, but instead a Range (using the ranges
library voted into C++20, see also its standalone implementation)
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
之类的东西中,然后直接 return 它们。
模板的问题是对于任何
template <typename T>
class C { };
任何两对 C<TypeA>
和 C<TypeB>
完全不相关 classes – 如果 TypeA
和 TypeB
仅在 const
-ness.
所以你真正想要的东西在技术上是不可能的。我现在不会提出新的解决方法,因为 已经 ,但试着看得更远一点:正如评论中已经指出的那样,你 可能 面临 XY 问题。
问题是:用户会用这样的列表做什么? She/he 可能正在对其进行迭代 - 或者访问单个元素。那为什么不把你的整个 class look/behave 变成一个列表呢?
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:
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
with 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;
}
其他运算符只会将操作转发给内部迭代器(递增、递减(如果可用)、比较等)。
我并不是说这是圣杯,是你在任何情况下都需要遵循的道路。但它是一个有价值的替代品,至少值得考虑...
你这里有一个非常复杂的结构:
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
而不是更传统的容器来存储共享指针吗?
如果您对所有要点的回答都是肯定的,那么为了实现您的目标,您可能需要设计一个新的 class 来充当您的 shared_ptr<list<shared_ptr<typeB>>>
的支架,而仅提供对元素的 const
访问。
但是,如果您对其中一个要点的回答是否定的,请考虑重新设计 l
类型。我建议从 std::vector<typeB>
开始,然后只一项一项地添加必要的修改。