Что означает 'const' в конце объявления метода класса?
Вопрос: Каково значение const
в таких объявлениях?
У меня есть следующий фрагмент кода на C++:
class foobar
{
public:
operator int () const;
const char* foo() const;
};
Я не совсем понимаю, какое значение имеет const
в этих объявлениях. Что конкретно означает const
после определения операторов и методов в классе? Как это влияет на поведение объектов данного класса? Буду признателен за объяснение!
5 ответ(ов)
Когда вы добавляете ключевое слово const
к методу, указатель this
фактически становится указателем на const
объект, и вы не сможете изменить данные членов этого объекта. (За исключением случаев, когда вы используете mutable
, но об этом позже).
Ключевое слово const
является частью сигнатуры функции, что означает, что вы можете реализовать два похожих метода: один, который вызывается, когда объект является const
, и другой, который не является таковым.
#include <iostream>
class MyClass
{
private:
int counter;
public:
void Foo()
{
std::cout << "Foo" << std::endl;
}
void Foo() const
{
std::cout << "Foo const" << std::endl;
}
};
int main()
{
MyClass cc;
const MyClass& ccc = cc;
cc.Foo();
ccc.Foo();
}
Это выведет:
Foo
Foo const
В методе без const
вы можете изменять члены экземпляра, чего нельзя сделать в версии с const
. Если вы измените объявление метода в приведенном выше примере на следующий код, вы получите ошибки.
void Foo()
{
counter++; // это работает
std::cout << "Foo" << std::endl;
}
void Foo() const
{
counter++; // это не скомпилируется
std::cout << "Foo const" << std::endl;
}
Это не совсем верно, потому что вы можете пометить член как mutable
, и тогда метод const
сможет его изменять. Это обычно используется для внутренних счетчиков и подобных вещей. Решение для этого будет следующим кодом.
#include <iostream>
class MyClass
{
private:
mutable int counter;
public:
MyClass() : counter(0) {}
void Foo()
{
counter++;
std::cout << "Foo" << std::endl;
}
void Foo() const
{
counter++; // Это работает, потому что counter - `mutable`
std::cout << "Foo const" << std::endl;
}
int GetInvocations() const
{
return counter;
}
};
int main(void)
{
MyClass cc;
const MyClass& ccc = cc;
cc.Foo();
ccc.Foo();
std::cout << "Foo было вызвано " << ccc.GetInvocations() << " раз" << std::endl;
}
Это выведет:
Foo
Foo const
Foo было вызвано 2 раз
Квалификатор const
означает, что методы могут быть вызваны для любого значения типа foobar
. Разница возникает, когда вы пытаетесь вызвать неконстантный метод на константном объекте. Рассмотрим, если ваш тип foobar
имел бы следующее дополнительное объявление метода:
class foobar {
...
const char* bar();
}
Метод bar()
является неконстантным и может быть вызван только для неконстантных значений.
void func1(const foobar& fb1, foobar& fb2) {
const char* v1 = fb1.bar(); // не скомпилируется
const char* v2 = fb2.bar(); // работает
}
Идея за const
заключается в том, чтобы обозначить методы, которые не будут изменять внутреннее состояние класса. Это мощная концепция, но на самом деле она не является строго соблюдаемой в C++. Это скорее обещание, чем гарантия. И это обещание часто нарушается и легко нарушается.
foobar& fbNonConst = const_cast<foobar&>(fb1);
Ответ Блэра в точку.
Однако стоит отметить, что к членам данных класса можно добавить квалификатор mutable
. Любой член, помеченный таким образом, может быть изменён в методе, определённом как const
, без нарушения контракта const
.
Вы можете использовать это, например, если хотите, чтобы объект запоминал, сколько раз был вызван какой-то определённый метод, при этом не затрагивая "логическую" постоянность этого метода.
Я хотел бы добавить следующий пункт.
Вы также можете сделать это const &
и const &&
Вот пример:
struct s {
void val1() const {
// *this здесь является const, поэтому эта функция не может изменять члены *this
}
void val2() const & {
// *this здесь является const&, значит, доступен только для lvalue-ссылок
}
void val3() const && {
// Этот объект, вызывающий эту функцию, должен быть const rvalue.
}
void val4() && {
// Этот объект, вызывающий эту функцию, должен быть только rvalue-ссылкой.
}
};
int main() {
s a;
a.val1(); // все в порядке
a.val2(); // все в порядке
// a.val3() не сработает, a не является rvalue, но если вызвать так:
std::move(a).val3(); // все в порядке, std::move делает его rvalue
}
Не стесняйтесь улучшать ответ. Я не эксперт.
Когда вы используете const
в сигнатуре метода (например, const char* foo() const;
), вы сообщаете компилятору, что память, на которую указывает this
, не может быть изменена внутри этого метода (в данном случае foo
). Это означает, что внутри метода вы можете безопасно обращаться к членам класса, не опасаясь их изменения. Кроме того, это позволяет вызывать этот метод на константных объектах данного класса.
Что такое Правило трёх?
Разница между const int*, const int * const и int * const?
Что означает T&& (двойной амперсанд) в C++11?
Новые возможности C++17: что стоит знать?
Имеют ли круглые скобки после имени типа значение при использовании new?