如何 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 – 如果 TypeATypeB 仅在 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_tagrandom_access_iterator_tagcontiguous_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_iterators.
在您的情况下,list 之上有一个 shared_ptr,因此如果您想要引用计数行为,则不能使用迭代器。

此时问题来了:你真的想要这种行为吗?

  • 您是否期望您的 typeA 实例被销毁,但您仍然有一些其他 typeA 实例具有相同的容器?
  • 您是否期望共享容器的所有 typeA 实例都被销毁,但您在运行时的其他地方仍然有对该容器的一些引用?
  • 您是否期望容器本身被销毁,但您仍然对某些元素有一些引用?
  • 您有任何理由使用 std::list 而不是更传统的容器来存储共享指针吗?

如果您对所有要点的回答都是肯定的,那么为了实现您的目标,您可能需要设计一个新的 class 来充当您的 shared_ptr<list<shared_ptr<typeB>>> 的支架,而仅提供对元素的 const 访问。

但是,如果您对其中一个要点的回答是否定的,请考虑重新设计 l 类型。我建议从 std::vector<typeB> 开始,然后只一项一项地添加必要的修改。