在 std::vector<T> 和 std::vector<unique_ptr<T>> 上均匀迭代

Posted

技术标签:

【中文标题】在 std::vector<T> 和 std::vector<unique_ptr<T>> 上均匀迭代【英文标题】:Iterating uniformly over std::vector<T> and std::vector<unique_ptr<T>> 【发布时间】:2017-07-24 17:29:32 【问题描述】:

我通常会遇到这样的情况:我引入一个抽象基类(称为Foo)来将不同子类的实例(称为BarBaz)存储在一个容器中(例如std::vector&lt;std::unique_ptr&lt;Foo&gt;&gt;) .为了便于说明,我将这些示例类放在这里:

class Foo 
public:
    virtual int getId() const = 0;
;

class Bar : public Foo 
public:
    Bar(int id) : id_(id) 
    int getId() const override  return id_; 
private:
    int id_;
;

class Baz : public Foo 
public:
    Baz(int id) : id_(id) 
    int getId() const override  return id_; 
private:
    int id_;
;

如果我实现一个函数来迭代std::vector&lt;std::unique_ptr&lt;Foo&gt;&gt;,它看起来像

template<class InputIterator>
void printIds(InputIterator first, InputIterator last) 
    for (; first != last; ++first)
        std::cout << (*first)->getId() << std::endl;

但是,如果我还想允许迭代 homogeneous 类型的向量(例如,std::vector&lt;Bar&gt;)而不重写整个函数(或可能的其他类似类型的函数)怎么办?我看到了两种明显的可能性:

1) 实现功能

template<class Type>
const Type & dereference(const Type &value) 
    return value;


template<class Type>
const Type & dereference(const std::unique_ptr<Type> &value) 
    return *value;

替换

std::cout << (*first)->getId() << std::endl;

通过

std::cout << dereference(*first).getId() << std::endl;

2) 实现功能

template<class Type>
int getId(const Type &value) 
    return value.getId();


template<class Type>
int getId(const std::unique_ptr<Type> &value) 
    return value->getId();

替换

std::cout << (*first)->getId() << std::endl;

通过

std::cout << getId(*first) << std::endl;

选项 1) 似乎可以统一处理Type &amp;(或const Type &amp;)和std::unique_ptr&lt;Type&gt;(甚至Type *const Type *)类型的引用。但是,我还没有看到这在生产代码中被大量使用。这是避免代码重复的常见模式吗?还是有更好的方法来处理这个问题?

【问题讨论】:

"这是避免代码重复的常见模式吗?" 不,因为人们通常不需要使用迭代指针容器然后生成的函数它遍历一个非指针容器。 我的答案是使用选项 1。创建一个“get”函数,该函数具有处理值和类指针类型的重载。 您是否考虑过间接迭代器包装器,例如boost::indirect_iterator? @JesperJuhl 是的,你是。 OP 希望统一迭代指针容器和项目容器中的 pointed to 元素。 我怀疑为两种迭代使用相同的代码对您来说实际上很重要。你确定你不是仅仅因为它“感觉正确”而不是为了获得有意义的东西而试图在这里进行微优化吗?看看@NicolBolas 怎么说。 【参考方案1】:

我会写get_ptr&lt;T&gt;

get_ptr&lt;T*&gt; 如果失败则返回 nullptr。

如果传递了对可转换为T* 的引用,则返回它。

如果传递了对可转换为T&amp; 的引用,它会返回一个指向它的指针。

否则,如果传递一个指针,如果指针为空则返回空,如果指针不为空则返回get_ptr&lt;T&gt;(*ptr)

否则,如果x.get()返回一个指针,它就返回get_ptr&lt;T&gt;(x.get())

现在printIds 写道:

template<class InputIterator>
void printIds(InputIterator first, InputIterator last) 
  for (; first != last; ++first)
    std::cout << get_ptr<Foo>(*first)->getId() << std::endl;

请注意,get_ptr 失败的可能性在这里相当明确。


如果您不想硬编码类型 T,我们可以更进一步。

如果get_obj_ptr 传递了一个指向指针的指针,并且该指针不为空,它会取消引用该指针并递归。如果为 null,则返回 nullptr 强制转换为相同类型。

如果传递了一个 .get() 为其返回指针的类,它会在 get_obj_ptr(x.get()) 上递归。

否则,如果向get_obj_ptr(x) 传递一个指针,则返回x

否则,如果 get_obj_ptr(x) 被传递一个左值,则返回 std::addressof(x)

与您的代码不同,它承认失败的可能性。

template<class InputIterator>
void printIds(InputIterator first, InputIterator last) 
  for (; first != last; ++first)
    auto* ptr = get_obj_ptr(*first);
    if (ptr)
      std::cout << ptr->getId() << std::endl;
    else
      std::cout << "null object" << std::endl;


作为一般规则,采用智能指针并假设它们永远不会为空是一个坏主意。

【讨论】:

我认为,总的来说,这与我对问题中 dereference 函数的想法很接近。然而,你是对的,如果智能指针指向空,这将不起作用。在这种情况下,您的方法肯定是更好的方法;我会接受这个作为答案。

以上是关于在 std::vector<T> 和 std::vector<unique_ptr<T>> 上均匀迭代的主要内容,如果未能解决你的问题,请参考以下文章

在现代 C++ 中将 std::array<std::array<T,N>> 转换为 std::vector<T>

std::vector<std::array<T, N>> 或 std::array<std::vector<T>,N> 类型的数组如何存储在内存中?

boost::ptr_vector 与 std::vector<std::unique_ptr<T>>? [关闭]

具有可访问构造函数的 std::vector< T >::iterator

调整 std::vector<std::unique_ptr<T>> 大小的性能

当 T 是原始类型时, std::vector<T>::clear() 的复杂性是多少?