![](/img/trans.png)
[英]Is using std::vector< std::shared_ptr<const T> > an antipattern?
[英]Const correctness with std::vector<str::shared_ptr<T>>
如果我有以下内容:
class Animal {};
class Penguin : public Animal {};
class Snake : public Animal {};
class Zoo
{
std::vector<std::shared_ptr<Animal>> animals;
public:
const std::vector<std::shared_ptr<Animal>>& GetAnimals() { return animals; }
std::shared_ptr<Penguin> AddPenguin()
{
auto result = std::make_shared<Penguin>();
animals.push_back(result);
return result;
}
std::shared_ptr<Snake> AddSnake()
{
auto result = std::make_shared<Snake>();
animals.push_back(result);
return result;
}
};
我想保持 const 的正确性,并能够添加以下方法:
const std::vector<std::shared_ptr<const Animal>>& GetAnimals() const
{
return animals;
}
但是,这不会编译,因为返回类型与动物不匹配。 由于const
嵌入类型深处,const_cast 无法转换。
但是,这会编译并表现出以下行为:
const std::vector<std::shared_ptr<const Animal>>& GetAnimals() const
{
return reinterpret_cast<const std::vector<std::shared_ptr<const Animal>>&>(animals);
}
我知道reinterpret_cast
可能很危险,但在这种情况下使用它有什么危险吗? 之间转换的类型是否具有相同的 memory 布局? 这样做的唯一效果是阻止调用者调用 Animal 的任何非常量方法吗?
更新我已经意识到这不完全正确。 调用者可以在向量的其中一个元素上调用.reset()
,这仍然会修改动物园。 即便如此,我仍然很好奇答案是什么。
更新更新我弄错了,我尝试的代码不小心复制了 shared_ptr,因此当向量为 const 时,无法重置向量中的 shared_ptr。
std::shared_ptr<Animal>
和std::shared_ptr<const Animal>
是根本不同的类型。 弄乱reinterpret_cast
可能会导致非常奇怪的错误(我想这主要是由于优化)。 您有两个选择:为每个std::shared_ptr<Animal>
创建一个新的std::shared_ptr<const Animal>
> ,或者返回一个复杂的代理类型(类似于向量的视图)。
也就是说,我质疑GetAnimals
的必要性。 如果Zoo
是指向动物的指针的集合,那么您不能提供像size
、 operator[]
和迭代器这样的访问函数吗? 这确实需要更多的努力,但如果你想要的只是一个返回整个向量的 function,为什么首先要有一个Zoo
class? 如果Zoo
包含其他数据并且管理的不仅仅是动物向量,那么我会制作一个单独的 class 来处理那部分, AnimalList
或其他东西。 然后 class 可以提供适当的访问功能。
您可能会尝试的其他方法是保留std::shared_ptr<std::vector<Animal>>
而不是您可以轻松转换为std::shared_ptr<const std::vector<Animal>>
。 根据您需要共享指针的原因,这可能相关也可能不相关。
您可能可以使用std::experimental::propagate_const
解决您的问题。 它是类指针类型的包装器,可以正确传播常量正确性。
const std::shared_ptr<Animal>
持有一个可变的Animal
。 检索对可变动物的可变引用是合法的,因为指针本身没有改变。 反之亦然, std::shared_ptr<Animal const>
将始终持有一个const Animal
。 您将不得不显式地放弃 constness 来改变持有的元素,这至少可以说是丑陋的。 另一方面,取消引用std::experimental::propagate_const<std::shared_ptr<Animal>>
如果它是 const,则返回Animal const&
如果它不是 const,则返回Animal&
。
如果您将共享指针包装在std::experimental::propagate_const
中,您可以为Zoo
配备一个 const 和一个非常量 getter 用于您的animals
vector 并具有 const-correctness (或者您可以使animals
成为公共数据成员,因为 getters不要做任何特别的事情。这会让你的 API 更透明) :
#include <vector>
#include <memory>
#include <experimental/propagate_const>
#include <type_traits>
class Animal {};
class Penguin : public Animal {};
class Snake : public Animal {};
class Zoo
{
template <typename T>
using pointer_t = std::experimental::propagate_const<std::shared_ptr<T>>;
std::vector<pointer_t<Animal>> animals;
public:
// const-getter
auto const& GetAnimals() const
{
return animals;
}
// non-const getter
auto& GetAnimals()
{
return animals;
}
std::shared_ptr<Penguin> AddPenguin()
{
auto result = std::make_shared<Penguin>();
animals.push_back(result);
return result;
}
std::shared_ptr<Snake> AddSnake()
{
auto result = std::make_shared<Snake>();
animals.push_back(result);
return result;
}
};
int main() {
Zoo zoo;
zoo.AddSnake();
// non-const getter will propagate mutability through the pointer
{
auto& test = zoo.GetAnimals()[0];
static_assert(std::is_same<Animal&, decltype(*test)>::value);
}
// const-getter will propagate const through the pointer
{
Zoo const& zc = zoo;
auto& test = zc.GetAnimals()[0];
static_assert(std::is_same<Animal const&, decltype(*test)>::value);
}
return 0;
}
https://godbolt.org/z/1rd8YraMc
我能想到的唯一缺点是令人沮丧的“实验性”命名空间以及 afaik MSVC 尚未实现它的事实,因此它不像它可能的那样可移植......
如果这让您感到困扰,您可以按照@Useless 的建议编写自己的propagate_const
包装器类型:
template <typename Ptr>
class propagate_const
{
public:
using value_type = typename std::remove_reference<decltype(*Ptr{})>::type;
template <
typename T,
typename = std::enable_if_t<std::is_convertible<T, Ptr>::value>
>
constexpr propagate_const(T&& p) : ptr{std::forward<T>(p)} {}
constexpr value_type& operator*() { return *ptr; }
constexpr value_type const& operator*() const { return *ptr; }
constexpr value_type& operator->() { return *ptr; }
constexpr value_type const& operator->() const { return *ptr; }
private:
Ptr ptr;
};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.