Wednesday, June 20, 2012

A very brief explanation of const member functions

Someone on ##c++-basic was asking about what making a member function const does.  Here's my reply:


 When designing classes in C++, one should keep in mind that there will be cases when your class is const, such as when it has been passed by const reference or simply when a const instance of it has been created.  It is therefore important to make sure that those operations which should be possible to perform on a const instance can indeed be performed.

As the C++ compiler does not know whether a member function modifies the members of the instance it is called on, member functions not specifically marked const may not be called on a const instance of the class.  The following, for example, is not allowed:
class PointBad {
  private:
    int x_;
  public:
    PointBad(int x) : x_(x) {}

    int GetX() {
        return this->x_;
    }
};

void f() {
    PointBad const p(5);
    std::cout << p.GetX(); // Compiler error: calling member function on const instance
}
In order to state that a mumber function does not modify the object it is called on, we can mark it const by appending const to the signature.  The result for this class is:
class PointGood {
  private:
    int x_;
  public:
    PointGood(int x) : x_(x) {}

    int GetX() const {
        return this->x_;
    }
};

void g() {
    PointGood const p(5);
    std::cout << p.GetX(); // works
}
As non-const member functions can do everything const member functions can do and more, one should keep all his member functions const unless they have a good reason to modify the instance.  This should be familiar from the way member functions should be private unless they are explicitly meant to be protected or public.

The C++ compiler makes sure that member functions marked const really don't modify the instance they are called on.  The following is illegal:
struct PointBroken {
  private:
    int x_;
  public:
    PointBroken(int x) : x_(x) {}

    int GetX() const {
        this->x_ = 7; // Compiler error: not allowed to modify this->x_.
        return this->x_;
    }  
};
This protection can be seen as the implicit 'this' pointer being of type PointBroken const* const instead of just PointBroken* const.  This justifies another restriction: calling a non-const member function on 'this' is forbidden, as we cannot be sure it does not have side effects.  For example, consider the following code:
struct PointAgainBroken {
  private:
    int x_;
    void SetXToSeven() {
        this->x_ = 7;
    }
  public:
    PointAgainBroken(int x) : x_(x) {}

    int GetX() const {
        this->SetXToSeven(); // Compiler error: not allowed to call non-const member function on this.
        return this->x_;
    }  
};
Clearly, allowing a call to SetXToSeven would allow behaviour identical to the previous example, which is what we do not want to allow.

The compiler errors generated by these mistakes can be somewhat cryptic; fortunately, they are fairly distinct, and can be seen below.  Comment them out one by one to see later ones.

No comments:

Post a Comment