Нестатические функции-элементы
Нестатический метод это функция, объявленная в спецификации элементов класса без спецификаторов static
или friend
.
(смотрите статические методы и определение друзей для понимания эффектов этих ключевых слов)
class S { int mf1(); // объявление нестатического метода void mf2() volatile, mf3() &&; // может иметь cv-квалификаторы и/или квалификатор // ссылки // объявление выше равнозначно двум раздельным объявлениям: // void mf2() volatile; // void mf3() &&; int mf4() const { return data; } // может быть определён встраиваемым virtual void mf5() final; // может быть виртуальным, может использовать final/override S() : data(12) {} // конструкторы также являются методами int data; }; int S::mf1() { return 7; } // если не объявлена как встроенная, то должна быть определена // в том-же пространстве имён
Конструкторы, деструкторы, и функции преобразования используют специальный синтаксис для своего объявления. Правила, описанные на данной странице, могут быть не применимы к данным функциям. Подробности смотрите на соответствующих страницах.
Явная функция-элемент объекта это нестатическая функция-элемент с явным параметром объекта. |
(начиная с C++23) |
Неявная функция-элемент объекта это нестатическая функция-элемент без явного объектного параметра (до C++23 это был единственный вид нестатической функции-элемента, поэтому в литературе её называли "нестатической функцией-элементом").
Содержание |
[править] Объяснение
Допускаются любые объявления функций с дополнительными элементами синтаксиса, которые доступны только для нестатических функций-элементов: спецификатор чистой функции, cv-квалификаторы, ссылочные-квалификаторы, спецификаторы final
и override
(начиная с C++11), и списки инициализации элементов.
Нестатические методы класса X
могут быть вызваны
X
используя оператор доступа к элементу классаX
X
Вызов нестатических методов класса X
из объекта, не принадлежащего классу X
, или типа, производного от X
, вызывают неопределённое поведение.
Внутри тела методов X
, любое выражение-идентификатор e (например идентификатор) который разрешается в нетиповой нестатический элемент X
или базового класса X
, преобразуется в выражение доступа к элементу (*this).e (если это уже не является частью выражения доступа к элементу). Этого не происходит в контексте определения шаблона, поэтому имя может иметь префикс this-> явно, чтобы стать зависимым.
struct S { int n; void f(); }; void S::f() { n = 1; // преобразуется в (*this).n = 1; } int main() { S s1, s2; s1.f(); // изменяет s1.n }
В предлах тела нестатического методов X
, любой неквалифицированный идентификатор, который разрешается в статический элемент, перечислитель или вложенный тип X
или базовый класс X
, преобразуется в соответствующий квалифицированный идентификатор:
struct S { static int n; void f(); }; void S::f() { n = 1; // преобразуется в S::n = 1; } int main() { S s1, s2; s1.f(); // изменяет S::n }
[править] Функции-элементы с cv-квалификаторами
Неявная функция-член объекта может быть объявлена с cv-квалифицированной последовательностью (const, volatile или комбинацией const и volatile), эта последовательность появляется после списка параметров в объявлении функции. Функции с разными cv-квалифицированными последовательностями (или без последовательности) имеют разные типы и поэтому могут перегружать друг друга.
В теле функции с последовательностью cv-квалификаторов, *this является cv-квалифицированным, например, в функции-элементе с квалификатором const могут нормально вызываться только другие функции-элементы с квалификатором const. (Функция-элемент без квалификатора const по-прежнему может быть вызывана, если применяется const_cast
или через путь доступа, который не включает this
.)
#include <vector> struct Array { std::vector<int> data; Array(int sz) : data(sz) {} // const метод int operator[](int idx) const { // этот указатель имеет тип const Array* return data[idx]; // преобразуется в (*this).data[idx]; } // неконстантный метод int& operator[](int idx) { // этот указатель имеет тип Array* return data[idx]; // преобразуется в (*this).data[idx] } }; int main() { Array a(10); a[1] = 1; // OK: тип a[1] это int& const Array ca(10); ca[1] = 2; // Ошибка: тип ca[1] это int }
Ссылочно-квалифицированные функции-элементыНеявная функция-элемент объекта может быть объявлена без ссылочного квалификатора, с левосторонним ссылочным квалификатором (символ
Примечание: в отличии от cv-квалификации, ссылочная квалификация не меняет свойства указателя |
(начиная с C++11) |
[править] Виртуальные и чисто виртуальные функции
Нестатический метод может быть объявлен виртуальным или чисто виртуальным. Подробнее смотрите виртуальные функции и абстрактные классы.
Явный параметр объектаНестатический метод можно объявить так, чтобы он принимал в качестве первого параметра явный объектный параметр, обозначаемый с префиксом в виде ключевого слова this. struct X { void foo(this X const& self, int i); // аналогично void foo(int i) const &; // void foo(int i) const &; // Ошибка: уже объявлено void bar(this X self, int i); // объект передаётся по значению: создается копия `*this` }; Для шаблонных методов, явное указание аргумента объекта позволяет выводить тип и категорию значения, эта возможность языка называется "вывод `this`" struct X { template <typename Self> void foo(this Self&&, int); }; struct D : X { }; void ex(X& x, D& d) { x.foo(1); // Self=X& move(x).foo(2); // Self=X d.foo(3); // Self=D& } Это позволяет дедуплицировать константные и не константные методы, для примера смотрите оператор индексации массива.
// CRTP свойство struct add_postfix_increment { template <typename Self> auto operator++(this Self&& self, int) { auto tmp = self; // Self выводится в "some_type" ++self; return tmp; } }; struct some_type : add_postfix_increment { some_type& operator++() { ... } }; Внутри тела функции с явным параметром объекта нельзя использовать указатель this: весь доступ к элементам должен осуществляться через первый параметр, как в статических методах: struct C { void bar(); void foo(this C c) { auto x = this; // ошибка: нет this bar(); // ошибка: нет неявного this-> c.bar(); // ok } }; Указатель на метод с явным параметром объекта это обычный указатель на функцию, не указатель на элемент: struct Y { int f(int, int) const&; int g(this Y const&, int, int); }; auto pf = &Y::f; pf(y, 1, 2); // ошибка: указатели на методы нельзя вызывать (y.*pf)(1, 2); // ok std::invoke(pf, y, 1, 2); // ok auto pg = &Y::g; pg(y, 3, 4); // ok (y.*pg)(3, 4); // ошибка: pg не является указателем на метод std::invoke(pg, y, 3, 4); // ok Методы с явным параметром объекта не могут быть статическими или виртуальными и они не могут иметь cv и ссылочных квалификаторов. |
(начиная с C++23) |
[править] Специальные функции-элементы
Некоторые методы являются специальными: при определённых обстоятельствах они определяются компилятором, даже если не определены пользователем. Вот они:
(начиная с C++11) |
(начиная с C++11) |
- Деструктор (до C++20)Ожидаемый деструктор (начиная с C++20)
Специальные методы вместе с операциями сравнения (начиная с C++20) являются единственными функциями, которые могут быть созданы по умолчанию, то-есть, определены с использованием = default вместо тела функции (смотри их страницы для подробного описания).
[править] Примечание
Макрос тест функциональности | Значение | Стандарт | Комментарий |
---|---|---|---|
__cpp_ref_qualifiers |
200710L | (C++11) | ссылочные квалификаторы |
__cpp_explicit_this_parameter |
202110L | (C++23) | Явный параметр объекта |
[править] Пример
#include <exception> #include <iostream> #include <string> #include <utility> struct S { int data; // простой конструктор преобразования (объявление) S(int val); // простой явный конструктор (объявление) explicit S(std::string str); // константный метод (определение) virtual int getData() const { return data; } }; // определение конструктора S::S(int val) : data(val) { std::cout << "конструктор1 вызван, данные = " << data << '\n'; } // этот конструктор имеет предложение catch S::S(std::string str) try : data(std::stoi(str)) { std::cout << "конструктор2 вызван, данные = " << data << '\n'; } catch(const std::exception&) { std::cout << "ошибка в конструктор2, строка '" << str << "'\n"; throw; // предложение catch конструктора всегда пробрасывает исключение дальше } struct D : S { int data2; // конструктор с аргументом по умолчанию D(int v1, int v2 = 11) : S(v1), data2(v2) {} // виртуальный метод int getData() const override { return data * data2; } // оператор присваивания только левосторонним значениям D& operator=(D other) & { std::swap(other.data, data); std::swap(other.data2, data2); return *this; } }; int main() { D d1 = 1; S s2("2"); try { S s3("не число"); } catch(const std::exception&) {} std::cout << s2.getData() << '\n'; ц D d2(3, 4); d2 = d1; // OK: присваивание левостороннему значению // D(5) = d1; // ОШИБКА: не существует допустимых перегрузок operator= }
Вывод:
конструктор1 вызван, данные = 1 конструктор2 вызван, данные = 2 ошибка в конструктор2, строка 'не число' 2 конструктор1 вызван, данные = 3
[править] Отчёты об ошибках
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
CWG 194 | C++98 | двусмысленность, когда нестатический метод имеет такое же имя, как и имя из окружающего класса |
добавлено явное ограничение именования |