Const Correct Class Design

suggest change

In a const-correct class, all member functions which don’t change logical state have this cv-qualified as const, indicating that they don’t modify the object (apart from any mutable fields, which can freely be modified even in const instances); if a const cv-qualified function returns a reference, that reference should also be const. This allows them to be called on both constant and non-cv-qualified instances, as a const T* is capable of binding to either a T* or a const T*. This, in turn, allows functions to declare their passed-by-reference parameters as const when they don’t need to be modified, without losing any functionality.

Furthermore, in a const correct class, all passed-by-reference function parameters will be const correct, as discussed in Const Correct Function Parameters, so that they can only be modified when the function explicitly needs to modify them.

First, let’s look at this cv-qualifiers:

// Assume class Field, with member function "void insert_value(int);".

class ConstIncorrect {
    Field fld;

  public:
    ConstIncorrect(Field& f); // Modifies.

    Field& getField();        // Might modify.  Also exposes member as non-const reference,
                              //  allowing indirect modification.
    void setField(Field& f);  // Modifies.

    void doSomething(int i);  // Might modify.
    void doNothing();         // Might modify.
};

ConstIncorrect::ConstIncorrect(Field& f) : fld(f) {} // Modifies.
Field& ConstIncorrect::getField() { return fld; }    // Doesn't modify.
void ConstIncorrect::setField(Field& f) { fld = f; } // Modifies.
void ConstIncorrect::doSomething(int i) {            // Modifies.
    fld.insert_value(i);
}
void ConstIncorrect::doNothing() {}                  // Doesn't modify.
class ConstCorrectCVQ {
    Field fld;

  public:
    ConstCorrectCVQ(Field& f);     // Modifies.

    const Field& getField() const; // Doesn't modify.  Exposes member as const reference,
                                   //  preventing indirect modification.
    void setField(Field& f);       // Modifies.

    void doSomething(int i);       // Modifies.
    void doNothing() const;        // Doesn't modify.
};

ConstCorrectCVQ::ConstCorrectCVQ(Field& f) : fld(f) {}
Field& ConstCorrectCVQ::getField() const { return fld; }
void ConstCorrectCVQ::setField(Field& f) { fld = f; }
void ConstCorrectCVQ::doSomething(int i) {
    fld.insert_value(i);
}
void ConstCorrectCVQ::doNothing() const  {}

// This won't work.
// No member functions can be called on const ConstIncorrect instances.
void const_correct_func(const ConstIncorrect& c) {
    Field f = c.getField();
    c.do_nothing();
}

// But this will.
// getField() and doNothing() can be called on const ConstCorrectCVQ instances.
void const_correct_func(const ConstCorrectCVQ& c) {
    Field f = c.getField();
    c.do_nothing();
}

We can then combine this with Const Correct Function Parameters, causing the class to be fully const-correct.

class ConstCorrect {
    Field fld;

  public:
    ConstCorrect(const Field& f);  // Modifies instance.  Doesn't modify parameter.

    const Field& getField() const; // Doesn't modify.  Exposes member as const reference,
                                   //  preventing indirect modification.
    void setField(const Field& f); // Modifies instance.  Doesn't modify parameter.

    void doSomething(int i);       // Modifies.  Doesn't modify parameter (passed by value).
    void doNothing() const;        // Doesn't modify.
};

ConstCorrect::ConstCorrect(const Field& f) : fld(f) {}
Field& ConstCorrect::getField() const { return fld; }
void ConstCorrect::setField(const Field& f) { fld = f; }
void ConstCorrect::doSomething(int i) {
    fld.insert_value(i);
}
void ConstCorrect::doNothing() const {}

This can also be combined with overloading based on constness, in the case that we want one behaviour if the instance is const, and a different behaviour if it isn’t; a common use for this is constainers providing accessors that only allow modification if the container itself is non-const.

class ConstCorrectContainer {
    int arr[5];

  public:
    // Subscript operator provides read access if instance is const, or read/write access
    // otherwise.    
          int& operator[](size_t index)       { return arr[index]; }
    const int& operator[](size_t index) const { return arr[index]; }

    // ...
};

This is commonly used in the standard library, with most containers providing overloads to take constness into account.

Feedback about page:

Feedback:
Optional: your email if you want me to get back to you:


Const correctness:
* Const Correct Class Design

Table Of Contents
8 Arrays
11 Loops
39 Streams
51 Unions
56 Lambdas
60 SFINAE
62 RAII
67 Sorting
84 RTTI
87 Scopes
104 Profiling
107 Recursion
114 Const correctness
117 Iteration
125 Alignment
134 Semaphore
136 Debugging
139 Mutexes
142 decltype