将 shared_ptr 的向量转换为 shared_ptr 到 const 的向量

13

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<std::vector<std::shared_ptr<int const>> const &>(v_);

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


1
与您的问题无关,但为什么您有一个指向 int(或 const int)的指针?如果您有一个指向“数组”的指针,那么使用向量会更合适吗? - Some programmer dude
1
你可以返回一个迭代器。 - Yola
1
“int” 只是一个例子,在实际代码中它是一个大类。 - Xoph
1
@Someprogrammerdude 我其实不太关心性能。如果返回一个副本,我也可以接受。但是为了有一个统一的接口,在非const情况下也最好返回一个副本。这反过来又使得在非const情况下无法返回&而只能返回const & - Xoph
@StoryTeller 我想要实现对 v_ 的范围循环,例如。在我的实际代码中,我更喜欢返回一个非 const 的 std::vector<std::shared_ptr<int>> & v(),以便外部用户甚至可以更改 v_ - Xoph
显示剩余3条评论
2个回答

6
避免复制容器的一种方法是提供转换迭代器,通过解引用对元素进行转换:
#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 是一种优雅的方法来实现“代理方法”。感谢您提供的好建议! - Xoph

3
放弃关于是否应该返回成员引用的讨论不谈,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_;,并且任何访问指针(例如您希望支持的基于范围的循环)都将保留 const-ness。
唯一的注意事项是它是一个可移动类型,因此复制向量的元素将需要更多的工作(例如通过调用具有向量本身的元素类型的std::experimental::get_underlying)。

谢谢您的建议。我实际上首先尝试使用propagate_const。但是,您提到的警告对我来说似乎相当强烈。在propagate_const中有一个成员函数get(),可以很好地完成我想要的事情,但仅适用于原始指针。没有get_underlying()成员函数以const-correct方式返回实际的指针类型。但是使用非成员get_underlying()会破坏整个const-correctness。 - Xoph
@Xoph 我不明白。成员与非成员在这里并不重要。你可以有一个隐式的 const propagate_const<T> * (this) 或者一个显式的 const propagate_const<T> &(参数),具有相同的 const 语义。 - Caleth
@Caleth 我想说的是:如果你有一个 const propagate_cast<shared_ptr<T>>,从中获取一个 shared_ptr<T const> 比我想象的要困难得多,并且需要打破常量正确性,因为你需要使用 get_underlying() 获取一个 shared_ptr<T>,然后从中转换成一个 shared_ptr<T const> - Xoph
@Xoph 是的,因为 shared_ptr<T>shared_ptr<const T> 是不同的类型(尽管有一个转换构造函数可以维护共享性)。 - Caleth
@Xoph [const] propagate_const<shared_ptr<T>> 可以满足你的需求,但是它是不可复制的。如果你可以自己定义API,并且不需要复制,那么使用 shared_ptr<const T> 没有任何意义。所以这里有一个很大的问题:你需要复制吗?另一方面,如果你不需要复制,那么使用 shared_ptr 的意义是什么,除了更简洁的代码? - Arne Vogel
@ArneVogel 我需要复本。 :) - Xoph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接