Сколько и какие варианты использования «const» в C ++?

129

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

tunnuz
источник
точно ищу этот вопрос: D
alamin 02

Ответы:

100

Пытаюсь собрать несколько применений:

Связывание некоторого временного объекта со ссылкой на 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неконстантной версии предпочтительнее неконстантной версии, operator[]и GCC создаст копию буфера резервной памяти, потому что мы можем изменить его, и это не должно влиять на данные a!

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

Чтобы конструктор-копия делал копии из константных объектов и временных файлов :

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

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

double const PI = 3.1415;

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

void PrintIt(Object const& obj) {
    // ...
}
Йоханнес Шауб - litb
источник
2
Не могли бы вы объяснить первое и третье использование в ваших примерах?
tunnuz
«Чтобы гарантировать вызываемому, что параметр не может быть NULL», я не понимаю, какое отношение имеет const к этому примеру.
Логан Капальдо, 02
ой, я так потерпел неудачу. я как-то начал писать про ссылки. Большое спасибо за стон :) Я, конечно,
уберу
3
Объясните, пожалуйста, первый пример. Для меня это не имеет особого смысла.
chikuba 08
28

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

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

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

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

Относительно того, зачем вы это сделали. Намного легче рассуждать об алгоритме, если вы знаете, что базовые данные не могут измениться. «const» помогает, но не гарантирует, что это будет достигнуто.

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

Пометка метода члена как const

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

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

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

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

JaredPar
источник
16

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

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

const char* ccp1;
char const* ccp2;

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

char* const cpc = &something_possibly_not_const;

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

const char* const ccpc = &const_obj;

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

Стив Фолли
источник
Окольными путями да, это так! Правило спирали по часовой стрелке описывает это лучше - начните с имени (kpPointer) и нарисуйте спираль по часовой стрелке, выходящую через жетон, и произнесите каждый жетон. Очевидно, что справа от kpPointer ничего нет, но он все равно работает.
Стив Фолли,
3

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

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

JoePerkins
источник