Такая же функция с const и без – когда и почему?

T& f() { // some code ... } const T& f() const { // some code ... } 

Я видел это пару раз (во вступительной книге, которую я изучал до сих пор). Я знаю, что первый const делает возвращаемое значение const, другими словами: unmodifiable. Полагаю, что вторая константа позволяет вызвать функцию для константных объявленных переменных.

Но почему у вас есть обе функции в одном и том же определении classа? И как компилятор отличает их? Я считаю, что второй f () (с константой) можно вызвать и для неконстантных переменных.

Но почему у вас есть обе функции в одном и том же определении classа?

Имея оба разрешения, вы можете:

  • вызвать функцию на изменяемом объекте и изменить результат, если хотите; а также
  • вызовите функцию на const объект и посмотрите только на результат.

Только с первого раза вы не можете называть его объектом const . С помощью только второго вы не можете использовать его для изменения объекта, на который он ссылается.

И как компилятор отличает их?

Он выбирает перегрузку const когда функция вызывается в const объекте (или с помощью ссылки или указателя на const ). В противном случае он выбирает другую перегрузку.

Я считаю, что второй f () (с константой) можно вызвать и для неконстантных переменных.

Если бы это была единственная перегрузка, то это могло бы произойти. При обеих перегрузках вместо него будет выбрана const перегрузка.

Но почему у вас есть обе функции в одном и том же определении classа?

Иногда вы хотите предоставить различную семантику для той же операции в зависимости от того, будет ли она вызываться в объекте const или non-const объекте. Возьмем пример classа std::string : –

 char& operator[](int index); const char& operator[](int index) const; 

В этом случае, когда operator[] вызывается через объект const вы не позволяете пользователю изменять содержимое строки.

 const std::string str("Hello"); str[1] = 'A'; // You don't want this for const. 

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

И как компилятор отличает их?

Компилятор проверяет, вызван ли этот метод для объекта const или non-const а затем соответствующим образом вызывает этот метод.

 const std::string str("Hello"); cout << str[1]; // Invokes `const` version. std::string str("Hello"); cout << str[1]; // Invokes non-const version. 

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

Второй, где наш хост-class находится в режиме только для чтения, также позволяет доступ только для чтения к его члену.

По умолчанию версия non-const вызывается, если она разрешена по правилам константы.

Один из наиболее распространенных примеров этого – с каким-то classом типа collection / array.

 class Array { private: MyType members[MySize]; public: MyType & operator[]( size_t index ); const MyType & operator[]( size_t index ) const; }; 

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

Теперь у нас может быть кто-то, кто использует class. Возможно, вы захотите установить значение.

 Array myArray; myArray[ 3 ] = myObject; 

Или вы можете читать только это:

 const Array& myArrayRef = getArrayRef(); // gets it to read const MyType & myValueRef = myArrayRef[ 3 ]; 

Таким образом, вы видите, что я могу использовать обозначение, чтобы установить значение и прочитать его. Как и в случае с operator[] , этот метод можно применить к любому методу.

Отборочные команды после вызова функции parens применяются к скрытому this параметру функций-членов:

Функция-член void Foo::bar() выглядит примерно так: void bar(Foo *this) . Но что произойдет, если объект Foo является const ?

 struct Foo { void bar(); }; const Foo f{}; f.bar(); 

Ну, поскольку Foo::bar() принимает Foo *this параметр, который не разрешен как const , приведенный выше f.bar(); не удается скомпилировать. Таким образом, нам нужен способ определить скрытый this параметр, и способ, которым C ++ решил сделать это, – позволить этим квалификаторам выйти за пределы функции parens.

Способ, которым компилятор отличает эти функции, во всех отношениях идентичен регулярной перегрузке функций, потому что это именно то, что есть, несмотря на странный синтаксис.

Кроме того, const является не единственным квалификатором. Вы также можете добавить volatile квалификаторы, а на C ++ 11 вы также можете указать квалификаторы эталонного значения lvalue и rvalue.


Причина, по которой нам нужны две почти идентичные копии этой функции, заключается в том, что нет прямого способа вытащить одну единственную разницу: разные типы возвращаемых данных. Если у нас есть объект const, и этот объект имеет getter, который возвращает ссылку на что-то, что он содержит, эта ссылка должна быть квалифицирована так же, как и общий объект.

 struct Foo { int i; int &get_i() const { return i; } }; int main() { const Foo f{}; f.get_i() = 10; // i should be const! } 

Вышеупомянутое не будет даже компилироваться, потому что внутри Foo::get_i() const , i является константой, и мы не можем вернуть ему неконстантную ссылку. Но если бы это было разрешено, это было бы неправильно, потому что мы не могли бы изменять членов объекта const. Поэтому Foo::get_i() const должен возвращать Foo::get_i() const ссылку на i .

 int const &Foo::get_i() const { return i; } 

Но мы должны иметь возможность модифицировать член неконстантного объекта,

 int main() { Foo f{}; f.get_i() = 10; // should be fine } 

поэтому мы не можем иметь только эту функцию. Нам нужна функция, которая возвращает неконстантную ссылку, когда сам объект Foo не является константой. Поэтому мы перегружаем функцию, основанную на constness объекта:

 struct Foo { int i; int const &get_i() const { return i; } int &get_i() { return i; } }; 

Если тело функции более усложняется, есть один возможный вариант, чтобы избежать дублирования:

 struct Foo { int i; int const &get_i() const { return i; } int &get_i() { return const_cast(const_cast(this)->get_i()); } }; 

То есть, неконстантная перегрузка делегирует ее реализацию в перегрузку константы, используя const_cast для исправления типов. Добавление константы всегда безопасно. Удаление const с помощью const_cast безопасно только тогда, когда мы точно знаем, что исходный объект не const. Мы знаем, что в этом случае, поскольку мы знаем, что мы добавили const в первую очередь к неконстантному объекту.

В рамках Qt есть очень хороший пример.

Взгляните на class QImage .

Существуют две публичные функции:

 const uchar* scanLine (int i) const; uchar* scanLine (int i); 

Первый – только для доступа к чтению. Второй – для случая, когда вы хотите изменить строку сканирования.

Почему это различие важно? Поскольку Qt использует неявный обмен данными . Это означает, что QImage не выполняет глубокую копию, если вы делаете что-то вроде этого:

 QImage i1, i2; i1.load("image.bmp"); i2 = i1; // i1 and i2 share data 

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

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

 #include  class test { public: test() : data_(0) {} int& f() { return data_; } const int& f() const { return data_ } private: int data_; }; int main(void) { const test rock; test paper; /* we can print both */ std::cout << rock.f() << std::endl; std::cout << paper.f() << std::endl; /* but we can modify only the non const one */ // rock.f() = 21; paper.f() = 42; } 

Как уже упоминалось ранее, вы можете использовать const и non const версии функций, зависящие от константы вызывающего объекта. Парадигма очень часто используется с operator[] для массивов. Способ избежать дублирования кода (взятый из книги Скотта Мейерса «Эффективный C ++») заключается в const_cast функции const в неконстантной перегрузке, такой как:

 // returns the position of some internal char array in a class Foo const char& Foo::operator[](std::size_t position) const { return arr[position]; // getter } // we now define the non-const in terms of the const version char& Foo::operator[](std::size_t position) { return const_cast( // cast back to non-const static_cast(*this)[position] // calls const overload ); // getter/setter } 
  • Почему элементы const должны быть инициализированы в инициализаторе конструктора, а не в его теле?
  • Почему «extern const int n» не работает должным образом?
  • Как инициализировать переменную const member в classе?
  • Почему строковые литералы const?
  • Почему у C ++ нет конструктора const?
  • Строковые литералы const?
  • __ атрибут __ ((const)) vs __attribute __ ((чистый)) в GNU C
  • Какая оптимизация предлагает const в C / C ++? (если есть)
  • Значение 'const' является последним в объявлении функции classа?
  • Постоянный ресурс ссылки C ++ (контейнерный адаптер)
  • Изменение константы int в C ++
  • Давайте будем гением компьютера.