Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

Chapter 128: Const Correctness

Section 128.1: The Basics

const correctness is the practice of designing code so that only code that needs to modify an instance is able to modify an instance (i.e. has write access), and conversely, that any code that doesn't need to modify an instance is unable to do so (i.e. only has read access). This prevents the instance from being modified unintentionally, making code less errorprone, and documents whether the code is intended to change the instance's state or not. It also allows instances to be treated as const whenever they don't need to be modified, or defined as const if they don't need to be changed after initialisation, without losing any functionality.

This is done by giving member functions const CV-qualifiers, and by making pointer/reference parameters const,

except in the case that they need write access.

class ConstCorrectClass { int x;

public:

int getX() const { return x; } // Function is const: Doesn't modify instance. void setX(int i) { x = i; } // Not const: Modifies instance.

};

// Parameter is const: Doesn't modify parameter.

int const_correct_reader(const ConstCorrectClass& c) { return c.getX();

}

// Parameter isn't const: Modifies parameter. void const_correct_writer(ConstCorrectClass& c) {

c.setX(42);

}

const ConstCorrectClass invariant; // Instance is const: Can't be modified.

ConstCorrectClass

variant; // Instance isn't const: Can be modified.

// ...

 

const_correct_reader(invariant); // Good. Calling non-modifying function on const instance. const_correct_reader(variant); // Good. Calling non-modifying function on modifiable instance.

const_correct_writer(variant); // Good. Calling modifying function on modifiable instance. const_correct_writer(invariant); // Error. Calling modifying function on const instance.

Due to the nature of const correctness, this starts with the class' member functions, and works its way outwards; if you try to call a non-const member function from a const instance, or from a non-const instance being treated as const, the compiler will give you an error about it losing cv-qualifiers.

Section 128.2: Const Correct Class Design

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.

GoalKicker.com – C++ Notes for Professionals

616

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.

GoalKicker.com – C++ Notes for Professionals

617

// 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 di erent 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.

Section 128.3: Const Correct Function Parameters

In a const-correct function, all passed-by-reference parameters are marked as const unless the function directly or indirectly modifies them, preventing the programmer from inadvertently changing something they didn't mean to change. This allows the function to take both const and non-cv-qualified instances, and in turn, causes the instance's this to be of type const T* when a member function is called, where T is the class' type.

struct Example {

 

void func()

{ std::cout << 3 << std::endl; }

GoalKicker.com – C++ Notes for Professionals

618

void func() const { std::cout << 5 << std::endl; }

};

void const_incorrect_function(Example& one, Example* two) { one.func();

two->func();

}

void const_correct_function(const Example& one, const Example* two) { one.func();

two->func();

}

int main() { Example a, b;

const_incorrect_function(a, &b); const_correct_function(a, &b);

}

// Output: 3 3 5 5

While the e ects of this are less immediately visible than those of const correct class design (in that const-correct functions and const-incorrect classes will cause compilation errors, while const-correct classes and const-incorrect functions will compile properly), const correct functions will catch a lot of errors that const incorrect functions would let slip through, such as the one below. [Note, however, that a const-incorrect function will cause compilation errors if passed a const instance when it expected a non-const one.]

//Read value from vector, then compute & return a value.

//Caches return values for speed.

template<typename T>

const T& bad_func(std::vector<T>& v, Helper<T>& h) {

//Cache values, for future use.

//Once a return value has been calculated, it's cached & its index is registered. static std::vector<T> vals = {};

int

v_ind = h.get_index();

//

Current

working index for

v.

int

vals_ind = h.get_cache_index(v_ind); //

Will be

-1 if cache index

isn't registered.

if (vals.size() && (vals_ind != -1) && (vals_ind < vals.size()) && !(h.needs_recalc())) { return vals[h.get_cache_index(v_ind)];

}

T temp = v[v_ind];

temp -= h.poll_device(); temp *= h.obtain_random();

temp += h.do_tedious_calculation(temp, v[h.get_last_handled_index()]);

// We're feeling tired all of a sudden, and this happens. if (vals_ind != -1) {

vals[vals_ind] = temp; } else {

v.push_back(temp); // Oops. Should've been accessing vals. vals_ind = vals.size() - 1;

h.register_index(v_ind, vals_ind);

}

GoalKicker.com – C++ Notes for Professionals

619