【问题标题】:Concatenating C++ iterator ranges into a const vector member variable at construction time在构造时将 C++ 迭代器范围连接到 const 向量成员变量中
【发布时间】:2010-10-19 22:04:48
【问题描述】:

我有一个 X 类,我在这里提供了一个 sn-p:

class X {
  public:
    template <typename Iter>
    X(Iter begin, Iter end) : mVec(begin, end) {}

  private:
    vector<Y> const mVec;
};

我现在想为这个类添加一个新的连接构造函数,比如:

template <typename Iter1, typename Iter2>
X(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2) : mVec(???) { ??? }

这样的构造函数会将两个范围 [begin1, end1) 和 [begin2, end2) 连接到 mVec 中。挑战是

1) 我想保留 mVec 上的 const,以便在 X 的其他方法中将其视为常量。

2) 我希望尽可能避免不必要的副本。也就是说,一种解决方案是有一个静态方法,它构造一个非常量临时到范围 1,插入范围 2 并返回它,然后定义连接构造函数到

template <typename Iter1, typename Iter2>
X(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2) 
  : mVec(concatenate(begin1, end1, begin2, end2)) { }

但我相信这至少会复制所有值一次。

【问题讨论】:

    标签: c++ stl constructor iterator concatenation


    【解决方案1】:

    很好的问题。我会尝试实现一个特定的迭代器包装类型,将两个范围变成一个范围。类似于:

    // compacted syntax for brevity...
    template <typename T1, typename T2>
    struct concat_iterator
    {
    public:
       typedef std::forward_iterator_tag iterator_category;
       typedef typename iterator_traits<T1>::value_type value_type;
       typedef *value_type pointer; 
       typedef &value_type reference;
    
       concat_iterator( T1 b1, T1 e1, T2 b2, T2 e2 ) 
          : seq1( b1 ), seq1end( e1 ), seq2( b2 ), seq2end( e2 );
       iterator& operator++() {
          if ( seq1 != seq1end ) ++seq1;
          else ++seq2;
          return this;
       }
       reference operator*() {
          if ( seq1 != seq1end ) return *seq1;
          else return *seq2;
       }
       pointer operator->() {
          if ( seq1 != seq1end ) return &(*seq1);
          else return &(*seq2);
       }
       bool operator==( concat_iterator const & rhs ) {
          return seq1==rhs.seq1 && seq1end==rhs.seq2 
              && seq2==rhs.seq2 && seq2end==rhs.seq2end;
       }
       bool operator!=( contact_iterator const & rhs ) {
          return !(*this == rhs);
       }
    private:
       T1 seq1;
       T1 seq1end;
       T2 seq2;
       T2 seq2end;
    };
    
    template <typename T1, typename T2>
    concat_iterator<T1,T2> concat_begin( T1 b1, T1 e1, T2 b2, T2 e2 )
    {
       return concat_iterator<T1,T2>(b1,e1,b2,e2);
    }
    template <typename T1, typename T2>
    concat_iterator<T1,T2> concat_end( T1 b1, T1 e1, T2 b2, T2 e2 )
    {
       return concat_iterator<T1,T2>(e1,e1,e2,e2);
    }
    

    现在你可以使用:

     class X {
     public:
        template <typename Iter, typename Iter2>
        X(Iter b1, Iter e1, Iter2 b2, Iter2 e2 ) 
          : mVec( concat_begin(b1,e1,b2,e2), concat_end(b1,e1,b2,e2) ) 
        {}
    
      private:
        vector<Y> const mVec;
    };
    

    或者(我刚刚想到)你不需要重新声明你的构造函数。让你的调用者使用辅助函数:

    X x( concat_begin(b1,e1,b2,e2), concat_end(b1,e1,b2,e2) );
    

    我没有检查代码,只是在我的脑海中输入了它。它可以编译也可以不编译,它可以工作也可以不工作……但是您可以以此为起点。

    【讨论】:

    • +1 用于聪明的解决方案...一个跨越两个迭代器范围的迭代器...不错
    • 使用 boost::iterator_facade 会更容易编写,但在这种情况下这可能是最好的选择。至少,在我们获得移动支持之前。
    • 看不到 concat_end 的必要性。您可以将生成的迭代器与值为 e2 的类型 T2 进行比较,然后生成一个布尔值。
    • @Martin York:我似乎不明白你的评论。您需要将两个相同类型的迭代器传递给 std::vector 构造函数。
    • Steve Dewhurst 显然早在 2002 年写了一篇 Dr. Dobbs Journal 文章,其中介绍了一个连接迭代器适配器:ddj.com/cpp/184401505
    【解决方案2】:

    最好放弃const(你为什么还要坚持呢?)。

    否则,您必须构建一个连接迭代器。代码挺多的,更多请查看this thread

    【讨论】:

    • 在我的例子中,向量成员变量在实例构建后不应该改变。将其设为 const 有助于编译器帮助我保证这一点。
    • 好吧,考虑到进行连接所需的代码量,如果您保留 const,您的代码更有可能出现错误。
    • SCFrench,X::mvec 在 X 构建完成后不会改变,这还不够安全吗?
    • 我希望编译器保证 X::mVec 不会被 X 的任何方法更改(以防我搞砸并在不应该修改它时不小心尝试修改它)。如果我删除 mVec 声明中的 const,那么 X 的任何非 const 方法都可以与 mVec 混淆。
    【解决方案3】:

    根据您的观点,C++ 最好或最差的特性之一是,您可以在必要时滥用它来完成工作。在这种情况下,const_cast 是受害者:

    template <typename Iter1, typename Iter2>
    X(Iter1 begin1, Iter1 end1, Iter2 begin2, Iter2 end2) : mVec(begin1, end1) {
        const_cast<vector<Y>&>(mVec).insert(mVec.end(), begin2, end2);
    }
    

    我可能有一些细节错误,我没有尝试编译这个。但它应该给你的想法。

    【讨论】:

    • 我正在研究一个相当复杂的迭代器包装器,它需要两个范围......这要简单得多,即使...... const_cast 哎哟!
    • 这实际上是未定义的行为。您不应该从定义为 const 的对象中删除 const。不过,您可以从绑定到非 const 对象的 const 引用中删除 const。
    • 未定义并不总是意味着不可预测。正如我所说,这绝对是滥用。
    • 由于我花了相当多的时间试图找出我从示例中得到的编译器错误,我将在这里提到它:不要忘记插入中的第二个 const_cast ".insert (const_cast&>(mvec).end(), begin2, end2):"
    • @Mark Ransom:“未定义并不总是意味着不可预测”如果我听说过,那是一个矛盾的白痴。为什么你认为这是可以预测的?这是一个非常非常糟糕的主意。
    【解决方案4】:

    您的静态方法可能没有您想象的那么糟糕,这取决于您的编译器所做的优化。而在 C++0x 中,移动构造函数将删除当前正在发生的任何复制。

    同时使用包装迭代器。代码不会像 avakar 链接到的线程那么糟糕,因为您只需要实现一个input iterator

    【讨论】:

      【解决方案5】:

      1) 我想保留 mVec 上的 const,以便在 X 的其他方法中将其视为常量。

      • 这是const 在成员变量上的奇怪用法。它违背了良好的设计。根据定义,构造是一个需要对象发生变化的过程。

      • 关于保持对象不可修改的要求——使用适当的封装。您应该使用const-member 函数来为您的类的客户公开基于您的mVec 的任何功能。

      2) 我希望尽可能避免不必要的副本。也就是说,一种解决方案是有一个静态方法,它构造一个非常量临时到范围 1,插入范围 2 并返回它,然后定义连接构造函数到

      您应该查看一般的移动构造函数和右值引用(C++0x 的承诺目标)。阅读此article

      【讨论】:

      • 我不明白你的第一个子弹。就我而言,一旦创建了对象,向量就应该保持不变。将其标记为 const 不会有助于强制执行吗?在成员变量上使用 const 会是什么?
      • const 成员变量不是静态的时,我没有看到太多使用它。您想要的是只读成员,并且 C++ 中没有构造可以按照您想要的方式进行操作。您最好的选择是使用 const 访问器,而不是由于 const_casts 而通过修改来调用 UB。
      猜你喜欢
      • 1970-01-01
      • 2020-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多