【问题标题】:Function template to populate a vector of shared_ptr to Base & Derived objects用于将 shared_ptr 向量填充到基础和派生对象的函数模板
【发布时间】:2019-08-13 06:01:21
【问题描述】:

考虑以下几点:

#include <memory>
#include <utility>
#include <vector>

class Base
{
public:
    Base()
        : x(0)
    {}
    int x;
    virtual ~Base() = default;
};

class Derived : public Base
{
public:
    Derived(double z0)
        : Base{}
        , z{ z0 }
    {}
    double z;
};

template<class T> // T might be either a Base or a Derived class.
std::vector<std::shared_ptr<Base>> MakeVector(std::size_t numElements)
{
    std::vector<std::shared_ptr<Base>> vec;
    for(auto &i : numElements) {     // Compiler error occurs here.
        vec.push_back(std::make_shared<T>());
    }
    return vec;
}

class Foo
{
public:
    Foo(std::size_t num_elements,
        std::vector<std::shared_ptr<Base>> bars = {})
    : m_bars{bars.empty() ? MakeVector<Base>(num_elements) : std::move(bars)}
    {}

    std::vector<std::shared_ptr<Base>> m_bars;
};

int main()
{
    const std::size_t foo1Size = 4;
    const std::size_t foo2Size = 5;

    // Create a vector of shared_ptr to 4 Base objects:
    Foo foo1 {foo1Size};

    // Create a vector of shared_ptr to 5 Derived objects:
    Foo foo2 {foo2Size, MakeVector<Derived>(foo2Size)};
}

这里的目标是创建请求数量的BaseDerived 对象,并用shared_ptrs 向这些对象填充std::vector

for 语句出现编译器错误:

错误:“开始”没有依赖于模板的参数 参数,因此“开始”的声明必须可用 [-fpermissive]

如果我将std::iteratorbeginend 一起使用,不幸的是,迭代器对每个push_back 都无效。无论如何,我需要迭代特定次数来填充向量。

这个问题有明显的解决办法吗?

【问题讨论】:

    标签: c++ templates inheritance shared-ptr stdvector


    【解决方案1】:
    1. 您不能在std::size_t 上使用range-based for loop。你可以改变

      for(auto &i : numElements) {
      

      for (std::size_t i = 0; i < numElements; i++) { 
      
    2. Derived 没有默认构造函数;您需要将参数传递给它以进行构造,否则 std::make_shared&lt;T&gt;() 将失败。您可以使用parameter packMakeVector 更改为以下内容:

      template<class T, class... Types> // T might be either a Base or a Derived class.
      std::vector<std::shared_ptr<Base>> MakeVector(std::size_t numElements, Types... args)
      {
          std::vector<std::shared_ptr<Base>> vec;
          for (std::size_t i = 0; i < numElements; i++) {
              vec.push_back(std::make_shared<T>(args...));
          }
          return vec;
      }
      

      然后像这样使用它

      // Create a vector of shared_ptr to 5 Derived objects:
      Foo foo2 {foo2Size, MakeVector<Derived>(foo2Size, 42)};
      

    【讨论】:

      【解决方案2】:

      numElements 是一个 std::size_t 类型,它没有定义 beginend 迭代器(这是为标准容器和用户定义类型定义的,而不是为原始类型定义的),这是 range-based for loop。因此你需要一个经典的for loop

      for(std::size_t index{ 0 }; index  < numElements; index ++) 
      { 
          vec.push_back(std::make_shared<T>());
      }
      

      或者只是一个while循环:

      while(numElements--)
      {
          vec.push_back(std::make_shared<T>());
      }
      

      其次,正如@songyuanyao 所指出的,Derived 类必须有一个default 构造函数才能使MakeVector 工作。你可以默认一个:

      Derived() = default;
      // or
      // Derived(double z0 = 0.0): Base{}, z{ z0 } {}
      

      或最好的类似 @songyuanyao 的答案,为构造函数参数提供额外的 varidic-template-args。

      【讨论】:

        【解决方案3】:
        for(auto &i : numElements) {     // Compiler error occurs here.
                vec.push_back(std::make_shared<T>());
            }
        

        for 范围是针对容器的。这里 numElements 是一个数字,使用经典的 for 循环。

        for(std::size_t i = 0; i < numElements; i++) { 
                vec.push_back(std::make_shared<T>());
            }
        

        【讨论】:

        • 从技术上讲,它适用于 ranges,即定义了迭代器和 end()begin() 的类型。不一定是容器,基于范围的for循环适用于纯数组,可以适配其他类型
        【解决方案4】:

        您不能对任意类型使用基于范围的 for 循环。该类型应与标准中的定义一起使用,

        // for ( range_declaration : range_expression ) loop_statement
        
        {
          auto && __range = range_expression ; 
          for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) 
          { 
            range_declaration = *__begin; 
            loop_statement 
          } 
        }
        

        即它应该已经定义了beginend,以及增量、取消引用和比较运算符,这不是普通整数类型的情况。在您的情况下,通常 for 是必需的,除非您想定义描述范围的类型。

        for (auto i = 0; i < numElements; ++i)
        

        Ofc,一个人可能很有创意,可以使用几乎任何类型的范围。像这样的东西(这绝不是推荐,只是一个例子):

        #include <utility> 
        #include <iostream>
        
        // Those should be in same namespace
        template<class T, template<typename,T,T> class iT, T _b, T _e>
        iT<T,_b,_e> begin(iT<T,_b,_e> v)
        {
            return iT<T,_b,_e> {_b};
        }
        
        template<class T, template<typename,T,T> class iT, T _b, T _e>
        iT<T,_b,_e> end(iT<T,_b,_e> v)
        {
            return iT<T,_b,_e> {_e};
        }
        
        template<class T, T begin, T end> 
        struct range {
            static const T _begin = T{begin};
            static const T _end = T{end};
        
            T value;
        
            range& operator++() { //prefix
                ++value;
                return *this;
            }
        
            T operator+(T inc) {
                return range<T,begin,end>{value + inc};
            }
        
            T operator-(T inc) {
                return range<T,begin,end>{value - inc};
            }
        
            T operator*() {return value;}
        
            bool operator != (range arg) { return value != arg.value; }
        };
        
        template<class T, T _b, T _e>
        range<T,_b,_e> operator++(range<T,_b,_e> &v, int) //postfix
        { 
                range<T,_b,_e> result {v};
                ++v;
                return result;
        }
        
        int main()
        {
            typedef range<size_t, 3, 10> SomeRange;
        
            for(auto i : SomeRange())
            {
                std::cout << i << std::endl;
            }
        }
        

        Derived 类至少需要一个默认构造函数,或者您的填充方法 MakeVector 应更改为放置正确的值

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-06-22
          • 1970-01-01
          • 1970-01-01
          • 2011-09-05
          • 1970-01-01
          • 2014-02-02
          • 2015-09-17
          • 1970-01-01
          相关资源
          最近更新 更多