【问题标题】:Using iterator to retrieve const values pointed to in containers使用迭代器检索容器中指向的 const 值
【发布时间】:2022-08-11 00:28:03
【问题描述】:

Const casting container value-types 似乎不可能。另一个问题中的评论建议将迭代器作为一种解决方案,但并未详细说明。 由于我似乎不能简单地将容器从非 const 转换为 const 版本作为函数参数,所以我到达 Iterators 可能能够完成这项工作。

我实际上有一个vector<shared_ptr<Thing> > 被视为const vector<shared_ptr<Thing const> >。 有了它,我打算在其他结构中使用shared_ptr<Thing const> 作为进一步的参考,而不允许这些结构改变Things。这些结构可以创建他们自己的对象,由他们自己的 shared_ptr 存储,如果他们想要在他们的容器中稍微不同的内容,同时仍然积极地与其他对象共享大部分 Things

所以我需要shared_ptr<const Thing>&const shared_ptr<const Thing>& 从迭代器通过序列。任何一种都足够了,但只是因为在这个例子中传递引用可以无所谓,因为 shared_ptr 的复制语义就是这样。 然而,即使只使用默认的const_iterator,由cbegin()c.end() 等检索,也会给我一个const shared_ptr<Thing>&

编辑:复制元素的矢量元素在技术上是一种方式,就像在另一个问题中一样,但由于界面原因是不受欢迎的。我要在这里重新解释,而不是复制。

关于解决方法可能存在的任何建议?

  • 使用一些代码,您描述的问题会更清楚。代码说一千多个字
  • @463035818_is_not_a_number 代码,描述了一般意图在上面的链接问题中提供。我将重新安排问题中的一些内容以使其成为焦点。至于用什么代码来解决它,这就是问题所在。
  • 这个问题应该是独立的。如果其他问题中的代码是您问题的基础,则将其添加到问题中(当然要适当注明出处)
  • 当您说“另一个问题中的评论建议使用迭代器作为解决方案,但没有详细说明”时,您是否指的是“通过fn({vc.begin(), vc.end()}) 调用它,请求显式转换”?没有太多细节可以说。它只是调用std::vector<const Thingy*> 的构造函数,因此该函数无法修改Thingys。
  • 这里没有低成本的解决方法。如果您需要传递vector<shared_ptr<Thing const>> 的参数,但您有vector<shared_ptr<Thing>>,则需要构建参数。我会提供一个辅助工厂函数vector<shared_ptr<Thing const>> to_const(vector<shared_ptr<Thing>> const& v) {/*code*/}

标签: c++ iterator c++17 const-correctness const-pointer


【解决方案1】:

根据您的情况,听起来像定义具有所需语义的自定义迭代器是安全且简单的方法。它在技术上是正确的,很难意外误用,而且速度相当快,只需要在迭代器取消引用时复制 shared_ptr 即可。

我总是推荐boost::iterator_facadeboost::iterator_adaptor 来创建迭代器类型。因为它将包含原始的 vector 迭代器作为“基础”实现,所以 iterator_adaptor 很有帮助。

class const_Thing_ptr_iterator :
    public boost::iterator_adaptor<
        const_Thing_ptr_iterator,                            // CRTP derived type
        std::vector<std::shared_ptr<Thing>>::const_iterator, // base iterator type
        std::shared_ptr<const Thing>,                        // value_type
        std::random_access_iterator_tag,                     // traversal type
        std::shared_ptr<const Thing>                         // reference
    >
{
public:
    const_Thing_ptr_iterator() = default;
    explicit const_Thing_ptr_iterator(base_type iter)
        : iterator_adaptor(iter) {}
};

reference 迭代器类型默认为std::shared_ptr&lt;const Thing&gt;&amp;,但在这种情况下它不能是引用,因为没有该类型的对象。通常该类会定义一些行为函数,如dereferenceincrement,但这里不需要:从基本向量迭代器到返回类型的唯一变化是operator*,默认reference dereference() const { return *base_reference(); } 有效从const std::shared_ptr&lt;Thing&gt;&amp;std::shared_ptr&lt;const Thing&gt; 的隐式转换很好。

该类也可以是一个以Thing 作为其类型参数的模板,以创建多个迭代器类型。

然后为了提​​供一个类似容器的视图对象,我们可以使用 C++20 的 std::ranges::subrange 来提供 begin()end() 以及其他一些帮助超出范围模板的东西:

#include <ranges>
class const_Thing_ptrs_view
  : public std::ranges::subrange<const_Thing_ptr_iterator>
{
public:
    explicit const_Thing_ptrs_view(const std::vector<std::shared_ptr<Thing>> &vec)
        : subrange(const_Thing_ptr_iterator(vec.begin()),
                   const_Thing_ptr_iterator(vec.end())) {}
};

或者,如果这不可用,则使用 begin()end() 的简单类:

class const_Thing_ptrs_view {
public:
    explicit const_Thing_ptrs_view(const std::vector<std::shared_ptr<Thing>> &vec)
        : m_begin(vec.begin()), m_end(vec.end()) {}
    const_Thing_ptr_iterator begin() const { return m_begin; }
    const_Thing_ptr_iterator end() const { return m_end; }
private:
    const_Thing_ptr_iterator m_begin;
    const_Thing_ptr_iterator m_end;
};

Demo on godbolt。 (由于this libstdc++ incompatibility,Clang 不喜欢范围代码;我不确定如何让 Godbolt 将其切换到 clang 的 libc++。)

【讨论】:

  • 由于似乎无法进行简单的重新解释,因此我可以看到您的解决方案是最合理的方法。可以将指针类型强制转换为取消引用,并将行为安全地隐藏起来。
【解决方案2】:

一个非常简单的方法可能是在内部维护一个指向 const 的指针向量 - 并将 const 转换为内部使用。

警告:不要认为这是一个粗心大意的邀请!如果你这样做,在某个时间点你将要打破东西。毕竟这些对象都是 const 是有原因的!

但是,在特定情况下,这个原因是纯粹的外部原因——如果不是公共接口,对象将永远不会得到const,因此再次将其丢弃是有效的在这个非常具体的情况下.

class DataOwner
{
public:
    std::vector<std::shared_ptr<Thing const>> const& data() const
    {
        return m_data;
    }

    void modify()
    {
        m_data.emplace_back(new Thing());

        at(0)->doSomething();
    }

    // for convenience of the user you might optionally duplicate
    // the const interface of `std::vector` here as well

private:
    std::vector<std::shared_ptr<Thing const>> m_data;

    Thing* at(size_t index)
    {
        // only one single location where const-casting
        // remember: generally a dangerous operation,
        // here one of the view valid use cases
        return const_cast<Thing*>(m_data[index].get());
        // don't forget to document in your own code WHY it is valid here
    }
};

【讨论】:

    【解决方案3】:

    将所有共享指针复制到新向量中很快就会变得非常昂贵,尤其是如果原始源向量被频繁更新并且引用实例因此需要一次又一次地获取更新。

    因此,我个人宁愿提供一个围绕std::vector 或围绕std::shared_ptr 的包装器,它只向数据所有者(届时将成为朋友)提供修改访问权限,而通用接口只允许非修改访问。但是,包装向量将需要在检索共享指针时复制共享指针,因此涉及引用计数,此外,解决方案变得更加复杂,因此在此处使用共享指针周围的包装器:

    struct Thing
    {
        void doSomething() { }
        void doSomethingElse() const { }
    };
    
    class DataOwner
    {
    public:
        class SharedPointer
        {
        public:
            SharedPointer(SharedPointer const&) = default;
            SharedPointer(SharedPointer&&) = default;
            SharedPointer& operator=(SharedPointer const&) = default;
            SharedPointer& operator=(SharedPointer&&) = default;
    
            Thing const& operator*() const
            {
                return *m_data;
            }
            Thing const* operator->() const
            {
                return m_data.get();
            }
            Thing const* get() const
            {
                return m_data.get();
            }
    
            // should be all of the public interface needed...
    
        private:
            friend class DataOwner;
    
            SharedPointer(Thing* t) : m_data(t) { }
            std::shared_ptr<Thing> m_data;
        };
    
        std::vector<SharedPointer> const& data() const
        {
            return m_data;
        }
    
        void modify()
        {
            m_data.emplace_back(SharedPointer(new Thing()));
    
            m_data[0].m_data->doSomething();
            // if needed at many places you might want to have a
            // convenience function, see below...
            at(0)->doSomething();
        }
    
        // for convenience of the user you might optionally duplicate
        // the const interface of `std::vector` here as well
    
    private:
        std::vector<SharedPointer> m_data;
    
        Thing* at(size_t index)
        {
            return m_data[index].m_data.get();
        }
    };
    
    int main()
    {
        DataOwner o;
        o.modify();
    
        o.data()[0]->doSomethingElse();
        // o.data()[0]->doSomething(); won't compile!
    
        return 0;
    }
    

    【讨论】:

    • 看来,这样每个DataOwner 都可以访问SharedPointer 的每个实例。较弱的限制。可以工作,不太理想。重新实现指针类型也不理想。但是,我怀疑在您的答案的基础上隐藏了一些模式,这些模式将指向设计我的代码的另一种方法,但是,现在我看到它为时已晚。
    • @Meph好吧,确实可以进行交叉访问。老实说,我默默地假设只有一个数据所有者......也同意单独的指针类型是一个缺点。你可以在内部维护 std::vector&lt;std::shared_ptr&lt;Thing const&gt;&gt; - 并将 const 丢弃在内部使用中。没有出现,因为它可能被解释为粗心大意的邀请......
    猜你喜欢
    • 2016-03-01
    • 2015-09-04
    • 1970-01-01
    • 2012-01-28
    • 2012-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-18
    相关资源
    最近更新 更多