繁体   English   中英

std::vector 的常量正确性<str::shared_ptr<t> > </str::shared_ptr<t>

[英]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是指向动物的指针的集合,那么您不能提供像sizeoperator[]和迭代器这样的访问函数吗? 这确实需要更多的努力,但如果你想要的只是一个返回整个向量的 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;
};

https://godbolt.org/z/eGPPPxef4

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM