Пространства имён
Варианты
Действия

Нестатические функции-элементы

Материал из cppreference.com
< cpp‎ | language
 
 
 
 

Нестатический метод это функция, объявленная в спецификации элементов класса без спецификаторов 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 могут быть вызваны

1) Для объекта типа X используя оператор доступа к элементу класса
2) Для объекта класса производного от X
3) Напрямую из тела других методов X
4) Напрямую из тела методов класса, производного от 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-квалификаторов класса X обрабатывается следующим образом:

  • без ссылочного квалификатора: неявный аргумент объекта имеет ссылку типа lvalue на cv-квалифицированный X и дополнительно может связывать подразумеваемый объектный аргумент с rvalue
  • левосторонний ссылочный квалификатор: неявный аргумент объекта имеет тип левосторонней ссылки на cv-квалифицированный X
  • правосторонний ссылочный квалификатор: неявный аргумент объекта имеет тип правосторонней ссылки на cv-квалифицированнный X
#include <iostream>
struct S
{
    void f() &  { std::cout << "lvalue\n"; }
    void f() && { std::cout << "rvalue\n"; }
};
 
int main()
{
    S s;
    s.f();            // выводит "lvalue"
    std::move(s).f(); // выводит "rvalue"
    S().f();          // выводит "rvalue"
}

Примечание: в отличии от cv-квалификации, ссылочная квалификация не меняет свойства указателя this: внутри правосторонне ссылочно квалифицированного метода, *this остаётся левосторонним выражением.

(начиная с 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:

// 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) являются единственными функциями, которые могут быть созданы по умолчанию, то-есть, определены с использованием = 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 двусмысленность, когда нестатический метод имеет такое же имя, как и
имя из окружающего класса
добавлено явное ограничение
именования

[править] Смотри также