【问题标题】:How to return a private pointer to a list of pointers as const?如何将指向指针列表的私有指针作为const返回?
【发布时间】:2019-06-05 04:55:37
【问题描述】:

我有一个指向指针列表的指针,作为私有变量。我还有一个 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&lt;list&lt;shared_ptr&lt;typeB&gt;&gt;&gt;shared_ptr&lt;const list&lt;shared_ptr&lt;typeB&gt;&gt;&gt; 工作正常。

是否可以将l 作为一个完整的常量返回,例如:

const shared_ptr<const list<shared_ptr<const typeB>>>

或至少喜欢:

shared_ptr<list<shared_ptr<const typeB>>> 

?

引用而不是指针不是一种选择。将l 声明为shared_ptr&lt;list&lt;shared_ptr&lt;const typeB&gt;&gt;&gt; 也不是想要的解决方案。

编辑:不再有“int”。

似乎不可能完全符合我的要求,但建议的解决方案很好。是的,复制指针是可以接受的。

糟糕的是,我没有立即输入 typeB。我知道引用相对于指针的一些优势,但我希望有一些类似的解决方案。

【问题讨论】:

  • 我更好奇为什么你有一个指向容器的指针?那有什么用例?为什么你有一个指向int 的指针容器?那有什么用例?
  • list&lt;int&gt; 怎么样?为什么这不是一个想要的解决方案?
  • @FantasticMrFox 除非有一些特殊要求,否则std::vector&lt;int&gt; 更适合 IMO。
  • 我认为您可能需要get a couple of good books 并阅读有关references 的信息。并且拥有指向单个 int 值的指针是毫无意义的(没有双关语的意思)。
  • @HD2000 我想你可能已经打算使用 int 作为一些更大对象的占位符了......但是如果你在展示代码之前明确解释,人们就会知道......另一方面,如果你写了shared_ptr&lt;list&lt;shared_ptr&lt;TypeB&gt;&gt;&gt;,事情也会很清楚。虽然如果你这样做更好,但我什至接受在这种非常具体的情况下不预先声明TypeB,因为你打算问什么仍然很清楚......

标签: c++ constants smart-pointers private-members container-data-type


【解决方案1】:

您可以从原始列表中创建 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&amp;(其中 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";
    }
}

【讨论】:

  • 它不会与原始shared_ptr 共享引用计数器
  • 你对列表构造函数的使用比我的 transform +1 更简洁。但最好明确一点,您返回的共享指针与l 指向的不是同一个列表。
  • 非常好。谢谢。
  • 我认为最好将列表本身设为 const 以及处理两个不同的列表,不允许修改副本将阻止用户插入新元素,同时假设已修改原始元素.. .
  • @HD2000 我添加了一个完全不需要复制列表的替代方案。
【解决方案2】:

当你将一个指向非常量的指针转换为一个指向常数的指针时,你有两个指针。此外,指向非常数的指针列表与指向常量的指针列表是完全不同的类型。

因此,如果你想返回一个指向常量指针列表的指针,你必须拥有一个常量指针列表。但你没有这样的清单。您有一个指向非常量的指针列表,并且这些列表类型不可相互转换。

当然,您可以将指向非常数的指针转换为指向常数的指针列表,但您必须了解它是一个单独的列表。指向前一种类型的指针不能指向后者。

所以,这里是一个转换列表的例子(我没有测试,可能包含错别字或错误):

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 对象的共享所有权是否对您的程序有意义 - 我假设这是对示例的简化。

还要重新考虑“引用而不是指针不是一个选项”是否是一个明智的要求。

【讨论】:

  • 从所有方面来看,这似乎是最接近的解决方案。谢谢。
  • @HD2000 Caleth's 从实际角度来看更好(如果需要,您可以继续使用list,但请考虑您是否真的需要list)。我的只是遵循任意的“无参考”要求。而且Ted使用构​​造函数比我的transform更简单,不用返回共享指针就可以使用。
【解决方案3】:

你的问题出在

但我不想混合引用和指针。只有指针更容易更简洁。

您在这里发现的是该语句错误list&lt;TypeB&gt; 可以绑定 const list&lt;TypeB&gt; &amp; 引用,并且列表的任何成员都不允许对 TypeB 对象进行任何修改。

class typeA {
    std::vector<typeB> l;
public:
    const std::vector<typeB> & getList() const { return l; };
};

如果你真的必须有const typeB,你可以改为返回一个l投影,它添加了const,但这不会是一个Container,而是一个Range(使用ranges库投票进入C++20,另见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_ptrs 包装在std::experimental::propagate_const 之类的东西中,然后直接返回它们。

【讨论】:

  • 如果将此应用于指针列表或向量,则 const-ness 仅适用于这些,而不适用于指向的对象。问题是:为什么应该有一个指针列表?好吧,这可能是一个多态场景,TypeB 只是基类...
  • 原始指针和引用的组合很容易,而智能指针则无法做到这一点。所以如果我有一堆处理指针的过程,插入引用是另一个困难,至少对我来说是这样。
  • @HD2000 我不关注。在ptr&lt;T&gt;ptr&lt;const T&gt; 之间切换时,您必须为原始指针和智能指针创建新的指针对象。相反,您应该更喜欢的是尽可能多地处理引用(和类似引用的包装器)
  • 不,我只是在说为什么我不使用引用——更容易一直使用指针,然后考虑某个对象是否是指针,以及如何将一个对象变成另一个对象。跨度>
【解决方案4】:

这里有一个非常复杂的结构:

shared_ptr<list<shared_ptr<typeB>>> l;

这是三个间接级别,其中两个具有引用计数生命周期管理,第三个是容器(并且不是内存连续的)。

自然,鉴于这种复杂的结构,将其转换为另一种类型并不容易:

shared_ptr<list<shared_ptr<const typeB>>>

请注意,std::list&lt;A&gt;std::list&lt;const A&gt; 在标准库的设计中是两种不同的类型。当您想将非修改句柄传递给您的容器时,您通常应该使用const_iterators。
在您的情况下,shared_ptrlist 之上,因此如果您想要引用计数行为,则不能使用迭代器。

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

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

如果您对所有要点的回答都是“是”,那么为了实现您的目标,您可能必须设计一个新类,该类将充当您的 shared_ptr&lt;list&lt;shared_ptr&lt;typeB&gt;&gt;&gt; 的持有者,同时仅提供 const 对元素的访问权限.

但是,如果您在其中一个要点上的答案是否定的,请考虑重新设计 l 类型。我建议从std::vector&lt;typeB&gt; 开始,然后只逐一添加必要的修改。

【讨论】:

  • const-iterator 的问题在于它使 shared_ptr 成为常量,而不是指针... .您不需要迭代器本身参与引用计数 - 只要它有效,共享指针就仍然存在,因此引用 is 尚未计数。
【解决方案5】:

模板的问题在于任何

template <typename T>
class C { };

任何两对C&lt;TypeA&gt;C&lt;TypeB&gt; 都是完全不相关的类——即使TypeATypeB 仅在const-ness 上不同时也是如此。

所以你真正想要的东西在技术上是不可能的。我暂时不会提出新的解决方法,因为已经有 ,但请尝试进一步了解:如 cmets 中所述,您可能面对 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_tagrandom_access_iterator_tagcontiguous_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;
}

其他运算符只会将操作转发给内部迭代器(递增、递减(如果可用)、比较等)。

我并不是说这是圣杯,是你在任何情况下都需要遵循的道路。但这是一个有价值的选择,至少值得考虑......

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-05-15
    • 1970-01-01
    • 2015-09-10
    • 1970-01-01
    • 1970-01-01
    • 2013-11-23
    • 1970-01-01
    相关资源
    最近更新 更多