Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

Наличие обоих позволяет вам:

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

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

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

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

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

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

Ответ 2

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

Иногда вы хотите предоставить различную семантику для той же операции в зависимости от того, вызывается ли она на объекте const object OR non-const. Возьмем пример класса 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 object OR non-const, а затем соответствующим образом вызывает этот метод.

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

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

Ответ 3

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

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

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

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

class Array
{
   private:
      MyType members[MySize];

   public:
      MyType & operator[]( size_t index );
      const MyType & operator[]( size_t index ) const;
};

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

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

Array myArray;
myArray[ 3 ] = myObject;

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

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

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

Ответ 4

Отборочные команды после вызова функции 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, и способ, которым С++ решил сделать это, - позволить этим квалификаторам выйти за пределы функции parens.

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

Кроме того, const не является единственным определителем. Вы также можете добавить квалификаторы volatile, а в С++ 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, и мы не можем вернуть ему неконстантную ссылку. Но если бы это было разрешено, это было бы неправильно, потому что мы не могли бы изменять членов объекта 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 не является константой. Поэтому мы перегружаем функцию, основанную на константе объекта:

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<int &>(const_cast<Foo const *>(this)->get_i()); }
};

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

Ответ 5

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

Взгляните на класс 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.

Ответ 6

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

#include <iostream>

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;

}

Ответ 7

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

// returns the position of some internal char array in a class Foo
const char& Foo::operator[](std::size_t position) 
{
    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<char&>( // cast back to non-const
        static_cast<const Foo&>(*this)[position] // calls const overload
    ); // getter/setter
}