Same function with const and without - When and why?

后端 未结 7 1744
终归单人心
终归单人心 2020-12-01 11:44
T& f() { // some code ... }
const T& f() const { // some code ... }

I\'ve seen this a couple of times now (in the introductory book I\'ve b

相关标签:
7条回答
  • 2020-12-01 12:23

    The first one with no const allows the caller to modify the object, which is in general a member of the class whose method is being called.

    The second one, where our host class is in read-only mode, also allows read-only access to its member.

    By the default, the non-const version is invoked if it is allowed under the rules of constness.

    One of the most common examples of this is with some kind of collection / array type class.

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

    Assuming they are implemented and that it might be a template or they are concrete types and sizes. I'm demonstrating the const overload.

    Now we can have someone using the class. You might want to set a value.

    Array myArray;
    myArray[ 3 ] = myObject;
    

    Or you might be only reading it:

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

    So you see I can use the notation to both set a value and read one. As with operator[], you can apply this technique to any method.

    0 讨论(0)
  • 2020-12-01 12:25

    But why would you have both functions in one and the same class definition?

    Having both allows you to:

    • call the function on a mutable object, and modify the result if you like; and
    • call the function on a const object, and only look at the result.

    With only the first, you couldn't call it on a const object. With only the second, you couldn't use it to modify the object it returns a reference to.

    And how does the compiler distinguish between these?

    It chooses the const overload when the function is called on a const object (or via a reference or pointer to const). It chooses the other overload otherwise.

    I believe that the second f() (with const) can be called for non-const variables as well.

    If that were the only overload, then it could. With both overloads, the non-const overload would be selected instead.

    0 讨论(0)
  • 2020-12-01 12:25

    It allows you to both have access to const instances data in a readonly way, while still being able to modify non const instances data.

    #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;
    
    }
    
    0 讨论(0)
  • 2020-12-01 12:34

    The qualifiers after function call parens apply to the hidden this parameter of member functions:

    A member function void Foo::bar() is sort of like this: void bar(Foo *this). But what happens if the Foo object is const?

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

    Well, since Foo::bar() takes a Foo *this parameter, which is not allowed to be const, the above f.bar(); fails to compile. So we need a way to qualify the hidden this parameter, and the way C++ choose to do that is to allow those qualifiers to go outside the function parens.

    The way the compiler distinguishes these functions is identical in every way to regular function overloading, because that's exactly what it is, despite the weird syntax.

    Furthermore, const is not the only qualifier. You can also add volatile qualifiers, and in C++11 you can also put lvalue and rvalue reference qualifiers.


    The reason we need two almost identical copies of this function is because there's no direct way to pull out the one single difference: the different return types. If we have a const object, and that object has a getter that returns a reference to something it contains, that reference needs to be qualified the same as the overall object.

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

    The above won't even compile because inside Foo::get_i() const, i is const, and we can't return a non-const reference to it. But if it were allowed, it would be wrong because we shouldn't be able to modify members of a const object. So Foo::get_i() const needs to return a const reference to i.

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

    But we should be able to modify a member of a non-const object,

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

    so we can't have only this function. We need a function that returns a non-const reference when the Foo object itself is not const. So we overload the function based on the const-ness of the object:

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

    If the function body is more complicate there's one possible option to avoid that duplication:

    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()); }
    };
    

    That is, the non-const overload delegates its implementation to the const overload, using const_cast to fix up the types. Adding const is always safe. Removing const using a const_cast is only safe when we know for sure that the original object is not const. We do know that in this case, because we know we added the const in the first place to a non-const object.

    0 讨论(0)
  • 2020-12-01 12:37

    There is a very nice example in the Qt framework.

    Take a look at the QImage class.

    There are two public functions:

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

    The first one is for read access only. The second one is for the case that you want to modify the scan line.

    Why is this distinction important? Because Qt uses implicit data sharing. This means, QImage does not immediately perform a deep copy if you do something like this:

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

    Instead, the data is copied only and only if you call a function that actually modifies one of the two images, like the non-const scanLine.

    0 讨论(0)
  • 2020-12-01 12:41

    As it was mentioned before, you can use const and non-const versions of functions depending on the const-ness of the invoking object. The paradigm is very often used with operator[] for arrays. A way to avoid code duplication (taken from Scott Meyers' book Effective C++) is to const_cast the const function return in a non-const overload, such as:

    // 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
    }
    
    0 讨论(0)
提交回复
热议问题