Recherche…


Syntaxe

  • classe ClassOne {public: bool non_modifying_member_function () const {/ * ... * /}};
  • int ClassTwo :: non_modifying_member_function () const {/ * ... * /}
  • annuler ClassTwo :: modifying_member_function () {/ * ... * /}
  • char non_param_modding_func (const ClassOne & one, const ClassTwo * deux) {/ * ... * /}
  • float parameter_modifying_function (ClassTwo & one, ClassOne * two) {/ * ... * /}
  • short ClassThree :: non_modding_non_param_modding_f (const ClassOne &) const {/ * ... * /}

Remarques

const correction de const est un outil de dépannage très utile, car elle permet au programmeur de déterminer rapidement quelles fonctions peuvent modifier le code par inadvertance. Il empêche également les erreurs involontaires, telles que celles présentées dans les Const Correct Function Parameters , de se compiler correctement et de passer inaperçues.

Il est beaucoup plus facile de concevoir une classe pour const exactitude, que d'ajouter plus tard const correct à une classe préexistante. Si possible, concevoir une classe qui peut être const correcte pour qu'il soit const correct, pour vous sauver et d' autres les tracas de modifier plus tard.

Notez que cela peut aussi être appliqué à la correction de la volatile si nécessaire, avec les mêmes règles que pour la correction des const , mais cela est beaucoup moins utilisé.

Réfractions:

ISO_CPP

Vendez-moi sur const correct

Tutoriel C ++

Les bases

const exactitude est la pratique de la conception du code de sorte que seul code qui a besoin de modifier une instance est en mesure de modifier une instance (c. -à- accès en écriture), et inversement, que tout code qui n'a pas besoin de modifier une instance est incapable de le faire so (c.-à-d. seulement accès en lecture). Cela empêche la modification involontaire de l'instance, ce qui réduit le risque d'erreur du code et indique si le code est destiné à modifier l'état de l'instance ou non. Cela permet également de traiter les instances en tant que const quand elles n'ont pas besoin d'être modifiées ou définies comme const si elles n'ont pas besoin d'être modifiées après l'initialisation, sans perdre aucune fonctionnalité.

Ceci est fait en donnant aux fonctions membres des qualificateurs de CV const , et en faisant des paramètres de pointeur / référence const , sauf dans le cas où ils ont besoin d'un accès en écriture.

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.

En raison de la nature de la correction de const, cela commence par les fonctions de membre de la classe et fonctionne vers l'extérieur; Si vous essayez d'appeler une fonction membre non const partir d'une instance const ou d'une instance non const traitée comme const , le compilateur vous donnera une erreur à propos de la perte des qualificatifs cv.

Const Correct Design de classe

Dans une classe const -correct, toutes les fonctions membres qui ne changent pas d'état ont this cv qualifiée en const , indiquant qu'elles ne modifient pas l'objet (à l'exception mutable champs mutable , qui peuvent être librement modifiés même dans les instances const ); Si une fonction const cv-qualifications renvoie une référence, cette référence doit également être const . Cela leur permet d'être appelés à la fois sur des instances constantes et non-cv, car un const T* est capable de se lier à un T* ou à un const T* . Ceci, à son tour, permet aux fonctions de déclarer leurs paramètres passés par référence en tant que const quand ils n'ont pas besoin d'être modifiés, sans perdre aucune fonctionnalité.

De plus, dans une classe const correcte, tous les paramètres de fonction passés par référence seront const , comme indiqué dans les Const Correct Function Parameters , afin qu'ils ne puissent être modifiés que lorsque la fonction doit explicitement les modifier.

Tout d'abord, regardons this qualificatifs cv:

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

Nous pouvons alors combiner ceci avec les Const Correct Function Parameters const , provoquant la correction complète de la classe.

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 {}

Cela peut aussi être combiné avec une surcharge basée sur la const , dans le cas où nous voulons un comportement si l'instance est const et un comportement différent si ce n'est pas le cas; une utilisation courante pour cela est les constainsers fournissant des accesseurs qui n'autorisent la modification que si le conteneur lui-même est 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]; }

    // ...
};

Ceci est couramment utilisé dans la bibliothèque standard, avec la plupart des conteneurs , à condition de prendre les surcharges const ness en compte.

Paramètres de fonction const constants

Dans une fonction const -correct, tous les paramètres passés par référence sont marqués comme const sauf si la fonction les modifie directement ou indirectement, empêchant le programmeur de modifier par inadvertance quelque chose qu'ils ne voulaient pas modifier. Cela permet à la fonction de prendre les deux const et des instances non-cv-qualifié, et à son tour, provoque l'instance est this être de type const T* lorsqu'une fonction membre est appelée, où T est le type de classe.

struct Example {
    void func()       { std::cout << 3 << std::endl; }
    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

Alors que les effets de cette situation sont moins immédiatement visibles que celles de const conception de classe correcte (dans ce const fonctions et const classes de provoquera des erreurs de compilation, alors que const classes -correct et const fonctions compileront correctement), const correcte les fonctions attraperont beaucoup d'erreurs que des fonctions const fausses laisseraient passer, comme celle ci-dessous. [Notez, cependant, que const la fonction provoque des erreurs de compilation si elle est adoptée une const par exemple quand il attend un non - const un.]

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

    return vals[vals_ind];
}

// Const correct version.  Is identical to above version, so most of it shall be skipped.
template<typename T>
const T& good_func(const std::vector<T>& v, Helper<T>& h) {
    // ...

    // We're feeling tired all of a sudden, and this happens.
    if (vals_ind != -1) {
        vals[vals_ind] = temp;
    } else {
        v.push_back(temp);  // Error: discards qualifiers.
        vals_ind = vals.size() - 1;
        h.register_index(v_ind, vals_ind);
    }

    return vals[vals_ind];
}

Const correctité comme documentation

L'une des choses les plus utiles à propos de la correction de const est qu'elle sert de moyen de documentation du code, fournissant certaines garanties au programmeur et aux autres utilisateurs. Ces garanties sont imposées par le compilateur en raison de la const , avec un manque de const à son tour, indiquant que le code ne les fournit pas.

const CV-Qualified Member Fonctions:

  • Toute fonction membre qui est const peut être supposée avoir l'intention de lire l'instance et:
    • Ne modifiera pas l'état logique de l'instance sur laquelle ils sont appelés. Par conséquent, ils ne doivent modifier aucune variable membre de l'instance sur laquelle ils sont appelés, à l'exception mutable variables mutable .
    • Ne doit appeler aucune autre fonction susceptible de modifier des variables membres de l'instance, à l'exception mutable variables mutable .
  • À l'inverse, toute fonction membre qui n'est pas const peut être supposée avoir l'intention de modifier l'instance et:
    • Peut ou non modifier l'état logique.
    • Peut ou non appeler d'autres fonctions qui modifient l'état logique.

Cela peut être utilisé pour faire des suppositions sur l'état de l'objet après l'appel d'une fonction membre donnée, même sans voir la définition de cette fonction:

// ConstMemberFunctions.h

class ConstMemberFunctions {
    int val;
    mutable int cache;
    mutable bool state_changed;

  public:
    // Constructor clearly changes logical state.  No assumptions necessary.
    ConstMemberFunctions(int v = 0);

    // We can assume this function doesn't change logical state, and doesn't call
    //  set_val().  It may or may not call squared_calc() or bad_func().
    int calc() const;

    // We can assume this function doesn't change logical state, and doesn't call
    //  set_val().  It may or may not call calc() or bad_func().
    int squared_calc() const;

    // We can assume this function doesn't change logical state, and doesn't call
    //  set_val().  It may or may not call calc() or squared_calc().
    void bad_func() const;

    // We can assume this function changes logical state, and may or may not call
    //  calc(), squared_calc(), or bad_func().
    void set_val(int v);
};

En raison des règles const , ces hypothèses seront en fait appliquées par le compilateur.

// ConstMemberFunctions.cpp

ConstMemberFunctions::ConstMemberFunctions(int v /* = 0*/)
  : cache(0), val(v), state_changed(true) {}

// Our assumption was correct.
int ConstMemberFunctions::calc() const {
    if (state_changed) {
        cache = 3 * val;
        state_changed = false;
    }

    return cache;
}

// Our assumption was correct.
int ConstMemberFunctions::squared_calc() const {
    return calc() * calc();
}

// Our assumption was incorrect.
// Function fails to compile, due to `this` losing qualifiers.
void ConstMemberFunctions::bad_func() const {
    set_val(863);
}

// Our assumption was correct.
void ConstMemberFunctions::set_val(int v) {
    if (v != val) {
        val = v;
        state_changed = true;
    }
}

const Paramètres de fonction:

  • Toute fonction avec un ou plusieurs paramètres qui sont const peut être supposée avoir l'intention de lire ces paramètres et:
    • Ne pas modifier ces paramètres, ni appeler les fonctions membres qui les modifieraient.
    • Ne doit pas transmettre ces paramètres à une autre fonction qui les modifierait et / ou appelerait les fonctions membres qui les modifieraient.
  • Inversement, toute fonction avec un ou plusieurs paramètres qui ne sont pas const peut être supposée avoir l'intention de modifier ces paramètres et:
    • Peut ou non modifier ces paramètres, ou appeler des fonctions membres qui les modifieraient.
    • Peut ou non transmettre ces paramètres à d'autres fonctions qui les modifieraient et / ou appeleraient les fonctions membres qui les modifieraient.

Cela peut être utilisé pour faire des hypothèses sur l'état des paramètres après avoir été transmis à une fonction donnée, même sans voir la définition de cette fonction.

// function_parameter.h

// We can assume that c isn't modified (and c.set_val() isn't called), and isn't passed
//  to non_qualified_function_parameter().  If passed to one_const_one_not(), it is the first
//  parameter.
void const_function_parameter(const ConstMemberFunctions& c);

// We can assume that c is modified and/or c.set_val() is called, and may or may not be passed
//  to any of these functions.  If passed to one_const_one_not, it may be either parameter.
void non_qualified_function_parameter(ConstMemberFunctions& c);

// We can assume that:
  // l is not modified, and l.set_val() won't be called.
  // l may or may not be passed to const_function_parameter().
  // r is modified, and/or r.set_val() may be called.
  // r may or may not be passed to either of the preceding functions.
void one_const_one_not(const ConstMemberFunctions& l, ConstMemberFunctions& r);

// We can assume that c isn't modified (and c.set_val() isn't called), and isn't passed
//  to non_qualified_function_parameter().  If passed to one_const_one_not(), it is the first
//  parameter.
void bad_parameter(const ConstMemberFunctions& c);

En raison des règles const , ces hypothèses seront en fait appliquées par le compilateur.

// function_parameter.cpp

// Our assumption was correct.
void const_function_parameter(const ConstMemberFunctions& c) {
    std::cout << "With the current value, the output is: " << c.calc() << '\n'
              << "If squared, it's: " << c.squared_calc()
              << std::endl;
}

// Our assumption was correct.
void non_qualified_function_parameter(ConstMemberFunctions& c) {
    c.set_val(42);
    std::cout << "For the value 42, the output is: " << c.calc() << '\n'
              << "If squared, it's: " << c.squared_calc()
              << std::endl;
}

// Our assumption was correct, in the ugliest possible way.
// Note that const correctness doesn't prevent encapsulation from intentionally being broken,
//  it merely prevents code from having write access when it doesn't need it.
void one_const_one_not(const ConstMemberFunctions& l, ConstMemberFunctions& r) {
    // Let's just punch access modifiers and common sense in the face here.
    struct Machiavelli {
        int val;
        int unimportant;
        bool state_changed;
    };
    reinterpret_cast<Machiavelli&>(r).val = l.calc();
    reinterpret_cast<Machiavelli&>(r).state_changed = true;

    const_function_parameter(l);
    const_function_parameter(r);
}

// Our assumption was incorrect.
// Function fails to compile, due to `this` losing qualifiers in c.set_val().
void bad_parameter(const ConstMemberFunctions& c) {
    c.set_val(18);
}

Bien qu'il soit possible de contourner la const constants et, par extension, d'interrompre ces garanties, cela doit être fait intentionnellement par le programmeur (tout comme rompre l'encapsulation avec Machiavelli , ci-dessus) et entraîner un comportement indéfini.

class DealBreaker : public ConstMemberFunctions {
  public:
    DealBreaker(int v = 0);

    // A foreboding name, but it's const...
    void no_guarantees() const;
}

DealBreaker::DealBreaker(int v /* = 0 */) : ConstMemberFunctions(v) {}

// Our assumption was incorrect.
// const_cast removes const-ness, making the compiler think we know what we're doing.
void DealBreaker::no_guarantees() const {
    const_cast<DealBreaker*>(this)->set_val(823);
}

// ...

const DealBreaker d(50);
d.no_guarantees(); // Undefined behaviour: d really IS const, it may or may not be modified.

Toutefois, en raison de ce qui nécessite le programmeur de dire très précisément le compilateur qu'ils ont l' intention d'ignorer const ness, et d' être incompatibles entre compilateurs, il est généralement prudent de supposer que const s'abstenir de le faire , sauf indication contraire code correct.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow