Сколько и как используются слова "const" в C++?

Как начинающий программист C++, есть некоторые конструкции, которые мне все еще кажутся очень неясными, одна из них const, Вы можете использовать его во многих местах и ​​с таким количеством различных эффектов, что для новичка практически невозможно выйти живым. Будет ли какой-нибудь гуру C++ когда-нибудь объяснять различные варианты использования, и / или почему не использовать их?

4 ответа

Решение

Попытка собрать некоторые использования:

Привязка некоторого временного к ссылке на const, чтобы продлить его жизнь. Ссылка может быть основой - и ее деструктор не обязательно должен быть виртуальным - правильный деструктор все еще называется:

ScopeGuard const& guard = MakeGuard(&cleanUpFunction);

Пояснение, используя код:

struct ScopeGuard { 
    ~ScopeGuard() { } // not virtual
};

template<typename T> struct Derived : ScopeGuard { 
    T t; 
    Derived(T t):t(t) { }
    ~Derived() {
        t(); // call function
    }
};

template<typename T> Derived<T> MakeGuard(T t) { return Derived<T>(t); }

Этот прием используется в служебном классе Александреску ScopeGuard. Когда временный объект выходит из области видимости, деструктор Derived вызывается правильно. В приведенном выше коде пропущены некоторые мелкие детали, но это большая проблема.


Используйте const, чтобы сказать, что другие методы не изменят логическое состояние этого объекта.

struct SmartPtr {
    int getCopies() const { return mCopiesMade; }
};

Используйте const для классов копирования при записи, чтобы компилятор помог вам решить, когда и когда вам не нужно копировать.

struct MyString {
    char * getData() { /* copy: caller might write */ return mData; }
    char const* getData() const { return mData; }
};

Объяснение: Возможно, вы захотите поделиться данными, когда копируете что-то, если данные оригинала и объекта копирования остаются неизменными. После того, как один из объектов изменит данные, вам теперь потребуется две версии: одна для оригинала и одна для копии. То есть вы копируете запись для любого объекта, так что теперь у них обоих есть своя собственная версия.

Используя код:

int main() {
    string const a = "1234";
    string const b = a;
    // outputs the same address for COW strings
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

Приведенный выше фрагмент кода печатает тот же адрес в моем GCC, поскольку используемая библиотека C++ реализует функцию копирования при записи. std::string, Обе строки, даже если они являются отдельными объектами, совместно используют одну и ту же память для своих строковых данных. Изготовление b non-const предпочтет неконстантную версию operator[] и GCC создаст копию буфера резервной памяти, потому что мы могли бы изменить его, и это не должно влиять на данные a!

int main() {
    string const a = "1234";
    string b = a;
    // outputs different addresses!
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

Для копирования-конструктора, чтобы сделать копии из const объектов и временных объектов:

struct MyClass {
    MyClass(MyClass const& that) { /* make copy of that */ }
};

Для создания констант, которые тривиально не могут быть изменены

double const PI = 3.1415;

Для передачи произвольных объектов по ссылке, а не по значению - для предотвращения возможной дорогой или невозможной передачи побочного значения

void PrintIt(Object const& obj) {
    // ...
}

На самом деле в C++ есть два основных применения const.

Константные значения

Если значение имеет форму переменной, члена или параметра, которые не будут (или не должны) изменяться в течение срока его службы, вы должны пометить его как const. Это помогает предотвратить мутации на объекте. Например, в следующей функции мне не нужно изменять переданный экземпляр Student, поэтому я отмечаю его const.

void PrintStudent(const Student& student) {
  cout << student.GetName();
}

Что касается того, почему вы бы это сделали. Об алгоритме гораздо проще рассуждать, если вы знаете, что базовые данные не могут измениться. "const" помогает, но не гарантирует, что это будет достигнуто.

Очевидно, что печать данных в cout не требует особых размышлений:)

Маркировка метода члена как const

В предыдущем примере я пометил Student как const. Но как C++ узнал, что вызов метода GetName() для student не приведет к изменению объекта? Ответ заключается в том, что метод был помечен как постоянный.

class Student {
  public:
    string GetName() const { ... }
};

Маркировка метода "const" делает 2 вещи. В первую очередь это говорит C++, что этот метод не будет изменять мой объект. Во-вторых, все переменные-члены теперь будут обрабатываться так, как если бы они были помечены как const. Это помогает, но не мешает вам изменять экземпляр вашего класса.

Это очень простой пример, но, надеюсь, он поможет ответить на ваши вопросы.

Постарайтесь понять разницу между этими 4 декларациями:

Следующие 2 объявления семантически идентичны. Вы можете изменить расположение ccp1 и ccp2, но вы не можете изменить то, на что они указывают.

const char* ccp1;
char const* ccp2;

Далее указатель является константным, поэтому, чтобы иметь смысл, он должен быть инициализирован, чтобы указывать на что-то. Вы не можете заставить его указывать на что-то другое, однако то, на что оно указывает, можно изменить.

char* const cpc = &something_possibly_not_const;

Наконец, мы объединяем два - поэтому объект, на который указывает указатель, не может быть изменен, и указатель не может указывать куда-либо еще.

const char* const ccpc = &const_obj;

Правило спирали по часовой стрелке может помочь распутать объявление http://c-faq.com/decl/spiral.anderson.html

В качестве небольшой заметки, как я читаю здесь, полезно заметить, что

const применяется ко всему, что находится непосредственно слева (кроме случаев, когда там ничего нет, в этом случае оно применяется к тому, что непосредственно справа от него).

Другие вопросы по тегам