【问题标题】:Turning vector of shared_ptr into vector of shared_ptr to const将 shared_ptr 的向量转换为 shared_ptr 到 const 的向量
【发布时间】:2018-07-16 10:44:59
【问题描述】:

让

class A
{
    std::vector<std::shared_ptr<int>> v_;
};

现在我想使用两个公共成员函数添加对 v_ 的访问权限

std::vector<std::shared_ptr<int>> const & v() { return v_; }

和

std::vector<std::shared_ptr<int const> const & v() const { TODO }

我不能用return v_; 替换TODO。

一种选择是不返回引用而是返回副本。除了明显的性能损失外,这也会使界面不太理想。

另一种选择是使TODO 等于return reinterpret_cast&lt;std::vector&lt;std::shared_ptr&lt;int const&gt;&gt; const &amp;&gt;(v_);

我的问题是,这是未定义的行为吗?或者,是否有更好的选择,最好不使用reinterpret_cast?

【问题讨论】:

  • 与您的问题无关,但为什么您有指向int(或const int)的指针?如果你有一个指向“数组”的指针,那么向量不是更有意义吗?
  • 你可以返回一个迭代器
  • int 只是一个例子,在实际代码中它是一个大类。
  • 关于“性能损失”,您测量过吗?现代编译器非常擅长优化这种情况,尤其是当它可以使用移动语义时。
  • @Someprogrammerdude 我实际上并不关心性能。我可以退回一份副本。但是我最好在非常量的情况下也返回一个副本,以获得统一的接口。这反过来又使得在非常量情况下无法返回&amp;,而不是const &amp;。

标签: c++ vector undefined-behavior reinterpret-cast const-correctness


【解决方案1】:

暂且不讨论是否应该返回对成员的引用...

std::vector 已经将它自己的const 限定符传播到它返回的引用、指针和迭代器。唯一的障碍是让它进一步传播到std::shared_ptr 的指针类型。您可以使用像 std::experimental::propagate_const 这样的类(希望是标准化的)来促进这一点。顾名思义,对于它包装的任何指针或类似指针的对象,它都会执行此操作。

class A
{
    using ptr_type = std::experimental::propagate_const<std::shared_ptr<int>>;
    std::vector<ptr_type> v_;
};

因此TODO 可以变成return v_;,并且对指针的任何访问(例如在您希望支持的基于范围的范围内)都将保留常量。

唯一需要注意的是,它是仅可移动的类型,因此复制向量的元素将需要更多工作(例如,通过调用 std::experimental::get_underlying)以及向量本身的元素类型。

【讨论】:

  • 感谢您的建议。我实际上首先尝试使用propagate_const。然而,你提到的警告对我来说似乎相当强烈。 propagate_const 中有一个成员函数 get() 可以很好地满足我的需求,但仅适用于原始指针。没有get_underlying() 成员函数以正确的方式返回实际的类指针类型。但是使用非成员 get_underlying() 会破坏整个 const 正确性。
  • @Xoph 我不关注。会员与非会员在这里无关紧要。你要么有一个隐式 const propagate_const&lt;T&gt; * (this) 要么有一个显式 const propagate_const&lt;T&gt; &amp; (参数),具有相同的 const 语义
  • @Caleth 我想说的是:如果你有一个const propagate_cast&lt;shared_ptr&lt;T&gt;&gt;,那么从它那里得到一个shared_ptr&lt;T const&gt; 比我想要的要难,并且涉及到破坏 const 正确性要求您使用get_underlying() 获取shared_ptr&lt;T&gt;,然后据我所知将其转换为shared_ptr&lt;T const&gt;。
  • @Xoph 是的,因为shared_ptr&lt;T&gt; 是与shared_ptr&lt;const T&gt; 不同的类型(尽管有一个转换构造函数保持共享性)
  • @Xoph [const] propagate_const&lt;shared_ptr&lt;T&gt;&gt; 会做你想做的事,除了不可复制。如果您可以自己定义 API,并且不需要副本,则没有理由使用shared_ptr&lt;const T&gt;。所以这是一个大问题:你需要副本吗?再说一次,如果您不需要需要副本,那么使用shared_ptr 除了更简洁的代码还有什么意义?
【解决方案2】:

避免复制容器的一种方法是提供在取消引用时转换元素的转换迭代器:

#include <vector>
#include <memory>
#include <boost/iterator/transform_iterator.hpp>

class A
{
    std::vector<std::shared_ptr<int> > v_;

    struct Transform
    {
        template<class T>
        std::shared_ptr<T const> operator()(std::shared_ptr<T> const& p) const {
            return p;
        }
    };

public:

    A() : v_{std::make_shared<int>(1), std::make_shared<int>(2)} {}

    using Iterator = boost::transform_iterator<Transform, std::vector<std::shared_ptr<int> >::const_iterator>;

    Iterator begin() const { return Iterator{v_.begin()}; }
    Iterator end() const { return Iterator{v_.end()}; }

};

int main() {
    A a;
    // Range access.
    for(auto const& x : a)
        std::cout << *x << '\n';
    // Indexed access.
    auto iterator_to_second_element = a.begin() + 1;
    std::cout << **iterator_to_second_element << '\n';
}

【讨论】:

  • 使用boost::transform_iterator 是一种优雅的“代理方法”。谢谢你的好建议!
猜你喜欢
  • 2020-11-19
  • 2021-06-21
  • 1970-01-01
  • 1970-01-01
  • 2010-11-24
  • 1970-01-01
  • 2020-01-08
  • 2017-08-06
  • 1970-01-01
相关资源
最近更新 更多