【问题标题】:How Do I define a Double Brackets/Double Iterator Operator, Similar to Vector of Vectors'?如何定义双括号/双迭代器运算符,类似于向量的向量?
【发布时间】:2011-04-14 21:11:14
【问题描述】:

我正在移植使用大量浮点数的代码,这可能会触发从 c 到 c++ 的 malloc 失败。我问了一个关于我应该使用向量还是双端队列的问题,Niki Yoshiuchi 慷慨地为我提供了这个安全包装类型的示例:

template<typename T>
class VectorDeque
{
private:
  enum TYPE { NONE, DEQUE, VECTOR };
  std::deque<T> m_d;
  std::vector<T> m_v;
  TYPE m_type;
  ...
public:
  void resize(size_t n)
  {
    switch(m_type)
    {
      case NONE:
      try
      {
        m_v.resize(n);
        m_type = VECTOR;
      }
      catch(std::bad_alloc &ba)
      {
        m_d.resize(n);
        m_type = DEQUE;
      }
      break;
    }
  }
};

我需要一个二维向量/双端队列,所以我将其修改为以下代码:

template<typename T>
class VectorDeque
{
private:
  enum STORAGE_CONTAINER { NONE, DEQUE, VECTOR };
  std::deque<std::deque<T> > x_d,y_d,z_d;
  std::vector<std::vector<T> > x_v,y_v,z_v;
  TYPE my_container;
public:
  void resize(size_t num_atoms, size_t num_frames)
  {
    switch(m_type)
    {
      case NONE:
      try
      {
        x_v.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   x_v[counter].resize(num_frames);
        y_v.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   y_v[counter].resize(num_frames);
        z_v.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   z_v[counter].resize(num_frames);
        my_container = VECTOR;
      }
      catch(std::bad_alloc &e)
      {
        x_d.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   x_d[counter].resize(num_frames);
        y_d.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   y_d[counter].resize(num_frames);
        z_d.resize(num_atoms);
 for (unsigned int couter=0;couter < num_frames; counter++)
   z_d[counter].resize(num_frames);
        my_container = DEQUE;
      }
      break;
    }
  }
};

我现在希望能够定义我的括号运算符,以便我可以有一个类似的语句 x[1][2] 直接访问我正在使用的 real 内存容器(由我的枚举变量的值给出。

我已经看过一些关于重写括号运算符的教程,但肯定不知道要重写双括号。

如何使双括号重载?

此外,您将如何重载双迭代器(如果我想使用迭代器,而不是直接索引)?

编辑1:

基于 Martin York/Matteo Italia 的解决方案,我设计了以下课程:

template<typename T>
class VectorDeque2D
{
public:

  class VectorDeque2D_Inner_Set
  {
    VectorDeque2D& parent;
    int   first_index;
  public:
    // Just init the temp object
    VectorDeque2D_Inner_Set(My2D& p, int first_Index) : 
      parent(p), 
      first_Index(first_index) {} 
    // Here we get the value.
    T& operator[](int second_index)  const 
    { return parent.get(first_index,second_index);}   
  };

  // Return an object that defines its own operator[] that will access the data.
  // The temp object is very trivial and just allows access to the data via 
  // operator[]
  VectorDeque2D_Inner_Set operator[](unsigned int first_index) { 
    return (*this, x);
  }


  void resize_first_index(unsigned int first_index) {
    try {
      my_vector.resize(first_index);
      my_container = VECTOR;
    }
    catch(std::bad_alloc &e) {
      my_deque.resize(first_index);
      my_container = DEQUE;
    }
  }

  void resize_second_index(unsigned int second_index) {
    try {
      for (unsigned int couter=0;couter < my_vector.size(); counter++) {
    my_vector[counter].resize(second_index);
      }
      my_container = VECTOR;
    }
    catch(std::bad_alloc &e) {
      for (unsigned int couter=0;couter < my_deque.size(); counter++) {
    my_deque[counter].resize(second_index);
      }
      my_container = DEQUE;
    }
  }
  void resize(unsigned int first_index,
          unsigned int second_index) {
    try {
      my_vector.resize(first_index);
      for (unsigned int couter=0;couter < my_vector.size(); counter++) {
    my_vector[counter].resize(second_index);
      }
      my_container = VECTOR;
    }
    catch(std::bad_alloc &e) {
      my_deque.resize(first_index);
      for (unsigned int couter=0;couter < my_deque.size(); counter++) {
    my_deque[counter].resize(second_index);
      }
      my_container = DEQUE;
    }    
  }
private:
  enum STORAGE_CONTAINER { NONE, DEQUE, VECTOR };

  friend class VectorDeque2D_Inner_Set;

  std::vector<std::vector<T> > my_vector;
  std::deque<std::deque<T> > my_deque;
  STORAGE_CONTAINER my_container;

  T& get(int x,int y) { 
    T temp_val;
    if(my_container == VECTOR) {
      temp_val = my_vector[first_index][second_index];
    }
    else if(my_container == DEQUE) {
      temp_val = my_deque[first_index][second_index];
    }

    return temp_val;
  }

};

终于有一个尺寸安全的 2D 容器了!!谢谢大家!

【问题讨论】:

    标签: c++ vector iterator operator-overloading deque


    【解决方案1】:

    主要有两种技术:

    1) 使用 operator() 而不是 operator[]。
    这是因为 operator() 允许多个参数。

    class My2D
    {
        public:
           int&   operator()(int x,int y)  { return pget(x,y);}
        private:
           int&   pget(int x,int y) { /* retrieve data from 2D storage */ }
    };
    

    2) 使用 operator[] 但返回一个中间对象。
    然后,您可以将第二个 operator[] 应用于中间对象。

    class My2D
    {
        public:
           class My2DRow
           {
               My2D& parent;
               int   x;
               public:
                   My2DRow(My2D& p, int theX) : parent(p), x(theX) {}     // Just init the temp object
                   int& operator[](int y)  const { return parent.pget(x,y);}   // Here we get the value.
           };
    
           // Return an object that defines its own operator[] that will access the data.
           // The temp object is very trivial and just allows access to the data via operator[]
           My2DRow operator[](int x)        { return My2DRow(*this, x);}
        private:
           friend class My2DRow;
           int&   pget(int x,int y) { /* retrieve data from 2D storage */ }
    };
    
    int main()
    {
        My2D   data;
        int&   val = data[1][2]; // works fine.
    
        // This is the same as
        My2D::My2DRow row  = data[1];
        int&          val2 = row[2]; 
    }
    

    我更喜欢第二种技术。
    这是因为它使原始代码保持不变并且更自然地阅读(在数组上下文中)。当然,您需要为实现 2D 数组的稍微复杂的代码在高层次上的简单性付出代价。

    【讨论】:

    • 不错!同样,您和 Matteo 提供的是我认为的“真正”解决方案。抱怨没有办法做到这一点的人只是对没有单数 [][] 运算符可以重载的事实感到困惑......
    【解决方案2】:
    如何使双括号重载?

    我没有完全理解你的问题,但是你必须重载括号,并让它们返回一个重载自己的括号运算符的对象。

    例如,如果您有一个向量向量,则工作已经完成:vector &lt; vector &lt; something &gt; &gt; 重载operator[],它返回一个vector&lt; something &gt;;反过来,它的括号运算符重载(并且它返回一个something 对象),所以你可以简单地这样做:

    vector<vector<something> > vec;
    // ...
    something s = vec[2][3];
    


    带有代理对象的示例:
    template <typename T>
    class Container
    {
    private:
        // ...
    
    
    public:
    
        // Proxy object used to provide the second brackets
        template <typename T>
        class OperatorBracketHelper
        {
            Container<T> & parent;
            size_t firstIndex;
        public:
            OperatorBracketHelper(Container<T> & Parent, size_t FirstIndex) : parent(Parent), firstIndex(FirstIndex) {}
    
            // This is the method called for the "second brackets"
            T & operator[](size_t SecondIndex)
            {
                // Call the parent GetElement method which will actually retrieve the element
                return parent.GetElement(firstIndex, SecondIndex);
            }
    
        }
    
        // This is the method called for the "first brackets"
        OperatorBracketHelper<T> operator[](size_t FirstIndex)
        {
            // Return a proxy object that "knows" to which container it has to ask the element
            // and which is the first index (specified in this call)
            return OperatorBracketHelper<T>(*this, FirstIndex);
        }
    
        T & GetElement(size_t FirstIndex, size_t SecondIndex)
        {
            // Here the actual element retrieval is done
            // ...
        }
    }
    

    (在适当的地方添加重载的 const 方法:))

    请注意,使用这种方法,与operator() 实现相比,您几乎不会损失任何东西,因为检索仍然在一个地方完成,对两个索引的使用没有限制,在执行时同时拥有两个索引检索,并且不返回“胖”临时对象(OperatorBracketHelper 与两个指针一样大,并且可以很容易地被编译器优化掉)。

    【讨论】:

    • 我希望能够使用双括号引用我的 VectorDeque 类型中包含的数据。您的示例与我尝试执行的操作不同,因为我没有尝试为 VectorDeque 定义 [],而是尝试为 VectorDeque 定义 [][],因为我的声明只是 VectorDeque();不是 VectorDeque
    • 嗯,这只是一个例子。请记住,没有 [][] 运算符 这样的东西,只有 operator[] 可能返回再次重载 operator[] 的类型。在这种情况下,您应该创建一个临时代理对象以在 VectorDeque 的 operator[] 中返回,该对象将保留对其父对象的引用,并在调用其 operator[] 时向其询问“正确”结果。我稍后会尝试添加一个示例。
    • 否;看看我刚刚添加的示例(希望我做对了 :))。
    • 见鬼,@Martin York 更快 :)
    • @Matteo Italia:很好的例子。只需返回值作为参考(以便它们是左值),从而允许您就地修改它们。 x[3][4] = 5;
    【解决方案3】:

    C++ 中没有“双括号”运算符。您需要做的是定义一个[] 运算符并让它返回对另一个对象的引用,该对象又可以响应它自己的[] 运算符。可以根据需要嵌套任意多个级别。

    例如,当您创建一个向量向量时,外部向量上的[] 运算符返回对内部向量之一的引用;该向量上的 [] 运算符返回对向量单个元素的引用。

    std::vector<std::vector<float> > example;
    std::vector<float> & first = example[0];  // the first level returns a reference to a vector
    float & second = example[0][0];  // the same as first[0]
    

    【讨论】:

    • @Jason,我认为您不理解我的回答。我没有说你做不到;我首先说最明显的答案是思考问题的错误方式,然后详细说明了获得所需内容的正确方法。我认为我的解释很简单直接,请告诉我我失败的地方。
    • 嗯,你似乎挂断了说你不能重载 [][] 因为它不是一个真正的运算符。我不关心重载 [][],就像我只想能够使用 [][] 为自定义类型索引事物,而无需嵌套。 Martin York 和 Matteo Italia 提供了这一点。你的答案在技术上对“T”是正确的,但你需要多思考一下;在这种情况下,您的回答会更有帮助。
    • @Jason,我可以看到以我的方式打开答案可能会给您带来错误的印象。当然,我并没有对此“挂断”,但这一关键事实似乎对于理解其余答案至关重要。很抱歉你没有发现它有帮助,也许下一个找到这个问题的人会有相反的意见。
    【解决方案4】:

    不要重载[] 运算符,重载() 运算符。

    查看此链接:Overloading Subscript Operator.

    我强烈建议在发布到 Stack Overflow 之前至少阅读 C++ FAQ Lite 一次。此外,搜索 Stack Overflow 也可能会产生一些有用的信息。

    【讨论】:

    • +1 用于使用 () 代替。当实现多维索引时,它经常被忽略。 (上帝知道我已经做了好几次了...... :)
    • 浏览了该部分和他的解决方案。它提到你可以使用[][]。我读了他的推理为什么你通常不想这样做,但我想保留它的矢量/双端特征,所以我认为在这种情况下它是值得的。但是你会怎么做呢?他没有提供太多细节。我在想我需要嵌套类之类的东西,但是如何在这种嵌套结构中保留 try/catch 处理?
    • @Jason R. Mick:与 C++FAQ 中的许多内容一样,它非常片面且带有判断力,我不同意。提供使用 [][] 使代码可读的能力是我推荐的方式,因为它使代码更易于阅读和维护。 (代价是让你的矩阵类更难阅读)。下面我提供了一个简单的模板,说明如何在不暴露实现细节的情况下做到这一点,从而提供灵活性(PS。这是一个相对标准的模式(不是我发明的)。
    • @LokiAstari 虽然有点极端,但我要补充一点,除了更具可读性外,使用 [][] 还可以将括号运算符用于多维函子。我用一个来创建绑定,用于向 opencl 的 n 维范围内核空间发出参数。
    【解决方案5】:

    我在answer to a previous question 中介绍了多维数组的重载运算符[]。

    我可能会非常相似地处理迭代器:有一个迭代器代表多维数组的“切片”(行或列),然后另一个迭代器代表该切片中的一个元素。

    【讨论】:

    • 您的答案似乎来自这里:parashift.com/c++-faq-lite/operator-overloading.html#faq-13.10 不过,这仍然不能回答我的问题。有没有办法使用[][]。我将接受一个将我的班级转变为嵌套班级的答案。但是,在一个坏的 malloc 上,我所有的 VectorDeque 实例都需要更改为 Deque,而不仅仅是那个......
    • @Jason:实际上,没有。常见问题解答中的答案来自古老的 Usenet 帖子,其中一些来自我。例如,罗伯特·马丁和我在 1996 年的一个帖子中提供了两个答案,我相信这早于常见问题解答中包含的内容:groups.google.com/group/comp.lang.c++.moderated/browse_frm/…
    猜你喜欢
    • 1970-01-01
    • 2013-09-19
    • 1970-01-01
    • 2011-02-07
    • 2012-07-17
    • 1970-01-01
    • 2019-01-17
    • 1970-01-01
    相关资源
    最近更新 更多