【问题标题】:Overloaded [] operator on template class in C++ with const / nonconst versionsC++ 中模板类上的重载 [] 运算符,具有 const / nonconst 版本
【发布时间】:2011-03-28 09:29:33
【问题描述】:

哇,标题好长啊。

这是我的问题。我在 C++ 中有一个模板类,我正在重载 [] 运算符。我有一个 const 版本和一个非常量版本,非常量版本通过引用返回,以便类中的项目可以这样更改:

myobject[1] = myvalue;

这一切都有效,直到我使用布尔值作为模板参数。这是一个显示错误的完整示例:

#include <string>
#include <vector>
using namespace std;

template <class T>
class MyClass
{
    private:
        vector<T> _items;

    public:

        void add(T item)
        {
            _items.push_back(item); 
        }

        const T operator[](int idx) const
        {
            return _items[idx];
        }

        T& operator[](int idx)
        {
            return _items[idx];
        }

};


int main(int argc, char** argv)
{
    MyClass<string> Test1;      //  Works
    Test1.add("hi");
    Test1.add("how are");
    Test1[1] = "you?";


    MyClass<int> Test2;         //  Also works
    Test2.add(1);
    Test2.add(2);
    Test2[1] = 3;


    MyClass<bool> Test3;        // Works up until...
    Test3.add(true);
    Test3.add(true);
    Test3[1] = false;           // ...this point. :(

    return 0;
}

错误是编译器错误,消息是:

error: invalid initialization of non-const reference of type ‘bool&’ from a temporary of type ‘std::_Bit_reference’

我已阅读并发现 STL 使用了一些临时数据类型,但我不明白为什么它适用于除 bool 之外的所有类型。

对此的任何帮助将不胜感激。

【问题讨论】:

  • 阅读这篇文章:gotw.ca/publications/mill09.htm
  • 可以修复。见下文。您只需要返回与 operator[] 返回相同的类型(通常是 T&(但并不总是像 vector 的情况))

标签: c++ templates return-by-reference


【解决方案1】:

vector&lt;bool&gt; 不像所有其他向量那样实现,也不像它们那样工作。你最好不使用它,并且不用担心你的代码是否无法处理它的许多特性 - 它通常被认为是坏事,被一些没有思想的 C++ 标准委员会成员强加给我们。

【讨论】:

  • 甚至标准委员会的成员也很快意识到自己的错误,并试图删除,但这就是标准的本质,它卡在那里......
【解决方案2】:

因为vector&lt;bool&gt;是专门做STL的,实际上并不符合标准容器的要求。

Herb Sutter 在 GOTW 文章中对此进行了更多讨论:http://www.gotw.ca/gotw/050.htm

【讨论】:

  • 太棒了。我多年来一直在使用 STL,但从未遇到过这种情况。感谢上帝,我将向量类抽象出来,否则我现在会处于一个受伤的世界。感谢您的回答。
  • 全部正确并解释了为什么它不能如上所述工作。但这并不意味着上述代码无法修复。只有当您开始尝试获取任何事情开始严重崩溃的特定元素的地址时,问题才会真正出现(这里不是这种情况)。
  • 我认为更大的问题是,正如那篇文章所提到的,STL 会根据输入对其行为进行分支。这意味着行为可能会因输入而大不相同。在我看来编码很差。
【解决方案3】:

vector&lt;bool&gt; 不是真正的容器。您的代码实际上是在尝试返回对单个位的引用,这是不允许的。如果您将容器更改为deque,我相信您会得到您期望的行为。

【讨论】:

    【解决方案4】:

    对您的课程进行一些单声道更改应该可以解决它。

    template <class T>
    class MyClass
    { 
        private:
            vector<T> _items;
    
        public:
    
            // This works better if you pass by const reference.
            // This allows the compiler to form temorary objects and pass them to the method.
            void add(T const& item)
            {
                _items.push_back(item);
            }
    
            // For the const version of operator[] you were returning by value.
            // Normally I would have returned by const ref.
    
            // In normal situations the result of operator[] is T& or T const&
            // But in the case of vector<bool> it is special 
            // (because apparently we want to pack a bool vector)
    
            // But technically the return type from vector is `reference` (not T&) 
            // so it you use that it should compensate for the odd behavior of vector<bool>
            // Of course const version is `const_reference`
    
            typename vector<T>::const_reference operator[](int idx) const
            {
                return _items[idx];
            }
    
            typename vector<T>::reference operator[](int idx)
            {
                return _items[idx];
            }
    };  
    

    【讨论】:

    • +1 用于寻找实际解决方案:)。我可能不会走这条路,只是因为我不喜欢向后弯腰来满足第三方库的细微差别。我正在编写的类抽象 STL,因此用户应该无法在 API 中看到来自 STL 的任何剩余工件:P。感谢您的意见!
    【解决方案5】:

    正如其他答案所指出的,在 vector 的情况下,提供了一种专门化来优化空间分配。

    但是,如果您使用 vector::reference 而不是 T&,您仍然可以使您的代码有效。事实上,在引用 STL 容器保存的数据时使用 container::reference 是一种很好的做法。

    T& operator[](int idx)
    

    变成

    typename vector<T>::reference operator[](int idx)
    

    当然还有一个用于 const 引用的 typedef:

    const T operator[](int idx) const
    

    这个变成了(删除无用的额外副本)

    typename vector<T>::const_reference operator[](int idx) const
    

    【讨论】:

      【解决方案6】:

      错误的原因是vector&lt;bool&gt; 专门用于打包存储在其中的布尔值,而vector&lt;bool&gt;::operator[] 返回某种允许您访问该值的代理。

      我认为解决方案不会是返回与 vector&lt;bool&gt;::operator[] 相同的类型,因为那样您只会将令人遗憾的特殊行为复制到您的容器中。

      如果您想继续使用vector 作为基础类型,我相信当MyClassbool 实例化时,可以通过使用vector&lt;MyBool&gt; 来解决布尔问题。

      它可能看起来像这样:

      #include <string>
      #include <vector>
      using namespace std;
      
      namespace detail
      {
          struct FixForBool
          {
              bool value;
              FixForBool(bool b): value(b) {}
              operator bool&() { return value; }
              operator const bool& () const { return value; }
          };
      
          template <class T>
          struct FixForValueTypeSelection
          {
              typedef T type;
          };
      
          template <>
          struct FixForValueTypeSelection<bool>
          {
              typedef FixForBool type;
          };
      
      }
      
      template <class T>
      class MyClass
      {
          private:
              vector<typename detail::FixForValueTypeSelection<T>::type> _items;
      
          public:
      
              void add(T item)
              {
                  _items.push_back(item);
              }
      
              const T operator[](int idx) const
              {
                  return _items[idx];
              }
      
              T& operator[](int idx)
              {
                  return _items[idx];
              }
      
      };
      
      
      int main(int argc, char** argv)
      {
          MyClass<string> Test1;      //  Works
          Test1.add("hi");
          Test1.add("how are");
          Test1[1] = "you?";
      
      
          MyClass<int> Test2;         //  Also works
          Test2.add(1);
          Test2.add(2);
          Test2[1] = 3;
      
      
          MyClass<bool> Test3;        // Works up until...
          Test3.add(true);
          Test3.add(true);
          Test3[1] = false;           // ...this point. :(
      
          return 0;
      }
      

      【讨论】:

      • Elegant ...我可能不会使用它(只是因为我没有对矢量的附件——无论如何这个类都是抽象矢量),但如果必须使用矢量,这是一个优雅的解决方案.
      • 不过,我自己也有第二个疑问。我仍然不建议使用&amp;my_vec[0] 的地址。
      • 在 STL 中使用元素的地址与在 STL 中使用迭代器一样危险。任何时候迭代器失效(通过添加新元素),地址都可能失效。无论哪种方式,这个示例类都只是包装了 vector 类。通过这样做,它只是传递与 vector 类相同的风险级别。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-04
      相关资源
      最近更新 更多