【问题标题】:Same function with const and without - When and why?与 const 和没有相同的功能 - 何时以及为什么?
【发布时间】:2015-01-07 17:46:28
【问题描述】:
T& f() { // some code ... }
const T& f() const { // some code ... }

我已经看过几次了(在我迄今为止一直在学习的介绍性书中)。我知道第一个 const 使返回值 const,换句话说:不可修改。我相信,第二个 const 允许也可以为 const 声明的变量调用该函数。

但是为什么你会在同一个类定义中同时拥有这两个函数呢?以及编译器如何区分这些?我相信第二个 f()(带有 const)也可以为非常量变量调用。

【问题讨论】:

  • 提示:第一个允许您修改 f() 返回的任何内容,这可能是正在调用的对象 f() 的一部分。
  • 第二个不允许修改定义该方法的类的状态。
  • 从对象中读取信息可能比提供写入它的方法更容易(例如读取/写入单个位数据结构)——那么const版本快,而非const版本可以写,所以他们的代码不同。

标签: c++ constants


【解决方案1】:

但是为什么你会在同一个类定义中同时拥有这两个函数呢?

两者兼得让您:

  • 在可变对象上调用函数,并根据需要修改结果;和
  • const 对象上调用函数,然后只查看结果。

只有第一个,您不能在 const 对象上调用它。只有第二个,你不能用它来修改它返回引用的对象。

编译器又是如何区分这些的?

当函数在const 对象上调用时(或通过对const 的引用或指针),它选择const 重载。否则它会选择另一个重载。

我相信第二个 f()(带有 const)也可以为非常量变量调用。

如果那是唯一的过载,那么它可以。对于这两种重载,将改为选择非const 重载。

【讨论】:

    【解决方案2】:

    但是为什么你会在同一个类定义中同时拥有这两个函数呢?

    有时您希望为同一操作提供不同的语义,具体取决于它是在 const 对象还是 non-const 对象上调用的。我们以std::string类为例:-

    char& operator[](int index);
    const char& operator[](int index) const;
    

    在这种情况下,当operator[] 通过const 对象调用时,您不会让用户更改字符串的内容。

    const std::string str("Hello");
    str[1] = 'A';     // You don't want this for const.
    

    另一方面,如果是非常量字符串,您可以让用户更改字符串的内容。这就是为什么不同的重载。

    而编译器又是如何区分这些的?

    编译器检查是否在const 对象或non-const 对象上调用了该方法,然后适当地调用该方法。

    const std::string str("Hello");
    cout << str[1];           // Invokes `const` version.
    
    std::string str("Hello");
    cout << str[1];           // Invokes non-const version.
    

    【讨论】:

      【解决方案3】:

      第一个没有 const 允许调用者修改对象,该对象通常是被调用方法的类的成员。

      第二个,我们的宿主类处于只读模式,也允许对其成员进行只读访问。

      默认情况下,如果在 constness 规则下允许,则调用非 const 版本。

      最常见的例子之一是某种集合/数组类型的类。

      class Array
      {
         private:
            MyType members[MySize];
      
         public:
            MyType & operator[]( size_t index );
            const MyType & operator[]( size_t index ) const;
      };
      

      假设它们已实现并且它可能是一个模板或者它们是具体的类型和大小。我正在演示 const 重载。

      现在我们可以让某人使用该类。您可能需要设置一个值。

      Array myArray;
      myArray[ 3 ] = myObject;
      

      或者你可能只是在阅读它:

      const Array& myArrayRef = getArrayRef(); // gets it to read
      const MyType & myValueRef = myArrayRef[ 3 ];
      

      所以你看我可以使用符号来设置一个值并读取一个值。 与operator[] 一样,您可以将此技术应用于任何方法。

      【讨论】:

        【解决方案4】:

        函数调用括号后的限定符适用于成员函数的隐藏this参数:

        成员函数void Foo::bar() 有点像这样:void bar(Foo *this)。但是如果 Foo 对象是 const 会发生什么?

        struct Foo {
            void bar();
        };
        
        const Foo f{};
        f.bar();
        

        好吧,既然Foo::bar()带了一个Foo *this参数,不允许是const,那么上面的f.bar();就编译失败了。所以我们需要一种方法来限定隐藏的 this 参数,而 C++ 选择这样做的方式是允许这些限定符超出函数括号。

        编译器区分这些函数的方式在各个方面都与常规函数重载相同,因为这正是它的本质,尽管语法很奇怪。

        此外,const 不是唯一的限定符。还可以添加volatile 限定符,在C++11 中还可以放置左值和右值引用限定符。


        我们需要这个函数的两个几乎相同的副本的原因是因为没有直接的方法来找出唯一的区别:不同的返回类型。如果我们有一个 const 对象,并且该对象有一个 getter,它返回对其包含的内容的引用,则该引用需要与整个对象一样限定。

        struct Foo {
          int i;
          int &get_i() const { return i; }
        };
        
        int main() {
          const Foo f{};
          f.get_i() = 10; // i should be const!
        }
        

        上面的代码甚至无法编译,因为在 Foo::get_i() consti 内部是 const,我们不能返回对它的非 const 引用。但如果允许,那就错了,因为我们不应该能够修改 const 对象的成员。所以Foo::get_i() const 需要返回一个对i 的常量引用。

        int const &Foo::get_i() const { return i; }
        

        但我们应该能够修改非常量对象的成员,

        int main() {
          Foo f{};
          f.get_i() = 10; // should be fine
        }
        

        所以我们不能只有这个功能。当 Foo 对象本身不是 const 时,我们需要一个返回非 const 引用的函数。所以我们根据对象的 const-ness 重载函数:

        struct Foo {
          int i;
          int const &get_i() const { return i; }
          int &get_i() { return i; }
        };
        

        如果函数体更复杂,有一种可能的选择可以避免这种重复:

        struct Foo {
          int i;
          int const &get_i() const { return i; }
        
          int &get_i() { return const_cast<int &>(const_cast<Foo const *>(this)->get_i()); }
        };
        

        也就是说,非 const 重载将其实现委托给 const 重载,使用 const_cast 来修复类型。添加 const 总是安全的。仅当我们确定原始对象不是 const 时,使用 const_cast 删除 const 才是安全的。我们确实知道在这种情况下,因为我们知道我们首先将 const 添加到非 const 对象。

        【讨论】:

          【解决方案5】:

          Qt 框架中有一个很好的例子。

          看看QImage 类。

          有两个公共函数:

          const uchar* scanLine (int i) const;
          uchar* scanLine (int i);
          

          第一个仅用于读取访问。第二个是针对你想修改扫描线的情况。

          为什么这种区别很重要? 因为Qt使用implicit data sharing。这意味着,如果您执行以下操作,QImage 不会立即执行深层复制:

          QImage i1, i2;
          i1.load("image.bmp");
          i2 = i1;                        // i1 and i2 share data
          

          相反,只有当您调用实际修改两个图像之一的函数(如非常量 scanLine)时,才会复制数据。

          【讨论】:

            【解决方案6】:

            它允许您以只读方式访问 const 实例数据,同时仍然能够修改非 const 实例数据。

            #include <iostream>
            
            class test
            {
              public:
                test() : data_(0) {}
            
                int& f() { return data_; }
                const int& f() const { return data_ }
            
              private:
                int data_;
            };
            
            int main(void)
            {
              const test rock;
              test paper;
            
              /* we can print both */
              std::cout << rock.f() << std::endl;
              std::cout << paper.f() << std::endl;
            
              /* but we can modify only the non const one */
              // rock.f() = 21;
              paper.f() = 42;
            
            }
            

            【讨论】:

              【解决方案7】:

              如前所述,您可以使用const 和非const 版本的函数,具体取决于调用对象的常量。该范例经常与operator[] 一起用于数组。避免代码重复的一种方法(取自 Scott Meyers 的《Effective C++》一书)是将const_castconst 函数以非常量重载形式返回,例如:

              // returns the position of some internal char array in a class Foo
              const char& Foo::operator[](std::size_t position) const
              {
                  return arr[position]; // getter 
              }
              
              // we now define the non-const in terms of the const version
              char& Foo::operator[](std::size_t position) 
              {
                  return const_cast<char&>( // cast back to non-const
                      static_cast<const Foo&>(*this)[position] // calls const overload
                  ); // getter/setter
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2016-06-20
                • 2021-01-11
                • 2021-02-26
                • 1970-01-01
                • 2016-10-24
                • 2017-03-26
                • 2014-05-24
                相关资源
                最近更新 更多