Операторы доступа к членам
Обеспечивает доступ к члену своего операнда.
| Название оператора | Синтаксис | Допускает перегрузку | Примеры прототипов (для classT) | |
|---|---|---|---|---|
| В определении класса | Вне определения класса | |||
| индексация |
a[b] | Да |
R& T::operator[](S b); | Н/Д |
a[...] (начиная с C++23) |
R& T::operator[](...); |
|||
| разыменование |
*a | Да |
R& T::operator*(); |
R& operator*(T a); |
| взятие адреса |
&a | Да |
R* T::operator&(); |
R* operator&(T a); |
| член объекта |
a.b | Нет | Н/Д | Н/Д |
| член указателя |
a->b | Да |
R* T::operator->(); | Н/Д |
| указатель на член объекта |
a.*b | Нет | Н/Д | Н/Д |
| указатель на член указателя |
a->*b | Да |
R& T::operator->*(S b); |
R& operator->*(T a, S b); |
Примечания
|
||||
Пояснение
Встроенный оператор индексации обеспечивает доступ к объекту, на который указывает операнд-указатель или операнд-массив.
Встроенный оператор разыменования обеспечивает доступ к объекту или функции, на которые указывает операнд-указатель.
Встроенный оператор взятия адреса создаёт указатель, указывающий на объект или функцию-операнд.
Операторы члена объекта и указателя на член объекта обеспечивают доступ к члену данных или функции-члену операнда-объекта.
Встроенные операторы члена указателя и указателя на член указателя обеспечивают доступ к члену данных или функции-члену класса, на который указывает операнд-указатель.
Встроенный оператор индексации
Выражения с оператором индексации имеют вид
expr1 [expr2 ] | (1) | |
expr1 [{expr , ...}] | (2) | (начиная с C++11) |
expr1 [expr2 , expr , ...] | (3) | (начиная с C++23) |
T» или пр-значением типа «указатель на T», а другое выражение (expr2 или expr1 соответственно) должно быть пр-значением нескоупированного перечислимого или целочисленного типа. Тип результата этого выражения — T. expr2 не может быть не заключённым в скобки выражением с оператором запятая.(начиная с C++23)
Встроенное выражение индексации E1[E2] в точности тождественно выражению *(E1 + E2), за исключением категории значения (см. ниже) и порядка вычисления(начиная с C++17): операнд-указатель (который может быть результатом преобразования массива в указатель и должен указывать на элемент массива или на позицию сразу за его последним элементом) корректируется так, чтобы указывать на другой элемент того же массива согласно правилам арифметики указателей, после чего выполняется разыменование.
При применении к массиву выражение индексации является lvalue, если массив является lvalue, и xvalue, если это не так(начиная с C++11).
При применении к указателю выражение индексации всегда является lvalue.
Тип T не может быть неполным типом, даже если размер или внутренняя структура T никогда не используются, как в &x[0].
| Использование не заключённого в скобки выражения с оператором запятая в качестве второго (правого) аргумента оператора индексации объявлено устаревшим. Например, |
(начиная с C++20) (до C++23 включительно) |
| Не заключённое в скобки выражение с оператором запятая не может быть вторым (правым) аргументом оператора индексации. Например, Чтобы использовать выражение с оператором запятая в качестве индекса, необходимы скобки, например | (начиная с C++23) |
При разрешении перегрузки для пользовательских операторов для каждого типа объекта T (возможно, с квалификаторами cv) в разрешении перегрузки участвует следующая сигнатура функции:
T& operator[](T*, std::ptrdiff_t); | ||
T& operator[](std::ptrdiff_t, T*); |
#include <iostream>
#include <map>
#include <string>
int main()
{
int a[4] = {1, 2, 3, 4};
int* p = &a[2];
std::cout << p[1] << p[-1] << 1[p] << (-1)[p] << '\n';
std::map<std::pair<int, int>, std::string> m;
m[{1, 2}] = "abc"; // uses the [{...}] version
}
Вывод:
4242
Встроенный оператор разыменования
Выражения с оператором разыменования имеют вид
*expr |
Операндом встроенного оператора разыменования должен быть указатель на объект или указатель на функцию; результатом является lvalue, ссылающееся на объект или функцию, на которые указывает expr. Если expr в действительности не указывает на объект или функцию, поведение не определено (кроме случая, указанного для typeid).
Указатель на (возможно, cv-квалифицированный) void разыменовать нельзя. Указатели на другие неполные типы разыменовывать можно, однако результирующее lvalue может использоваться только в контекстах, допускающих lvalue неполного типа, например при инициализации ссылки.
При разрешении перегрузки для пользовательских операторов для каждого типа T, который является типом объекта (возможно, с квалификаторами cv) или типом функции (без квалификаторов const или ref), в разрешении перегрузки участвует следующая сигнатура функции:
T& operator*(T*); |
#include <iostream>
int f() { return 42; }
int main()
{
int n = 1;
int* pn = &n;
int& r = *pn; // lvalue can be bound to a reference
int m = *pn; // indirection + lvalue-to-rvalue conversion
int (*fp)() = &f;
int (&fr)() = *fp; // function lvalue can be bound to a reference
[](...){}(r, m, fr); // removes possible "unused variable" warnings
}
Встроенный оператор взятия адреса
Выражения с оператором взятия адреса имеют вид
&expr | (1) | |
&class ::member | (2) | |
&splice-specifier | (3) | (начиная с C++26) |
T, operator& создаёт и возвращает пр-значение типа T* с теми же квалификаторами cv, указывающее на объект или функцию, обозначенные операндом. Если операнд имеет неполный тип, указатель сформировать можно, однако если этим неполным типом окажется класс, определяющий собственный operator&, не определено, будет использован встроенный оператор или перегрузка. Для операндов типа с пользовательским operator& можно использовать std::addressof, чтобы получить настоящий указатель. Обратите внимание: в отличие от C99 и более поздних версий C, здесь нет особого случая для применения унарного оператора & к результату унарного оператора *.
| Если expr обозначает функцию-член с явным объектом, expr должен быть квалифицированным идентификатором. Применение | (начиная с C++23) |
&C::member, результатом является пр-значение — указатель на функцию-член или указатель на член данных типа T класса C. Обратите внимание: ни &member, ни C::member, ни даже &(C::member) нельзя использовать для инициализации указателя на член.&([:r:]) никогда не является указателем на член.Если операнд обозначает перегруженную функцию или шаблон функции, адрес можно получить только в том случае, если перегрузку можно разрешить исходя из контекста. Подробности см. в разделе Адрес перегруженной функции.
При разрешении перегрузки для пользовательских операторов этот оператор не добавляет никаких дополнительных сигнатур функций: встроенный оператор взятия адреса не применяется, если существует перегруженный operator&, являющийся подходящей функцией.
void f(int) {}
void f(double) {}
struct A { int i; };
struct B { void f(); };
int main()
{
int n = 1;
int* pn = &n; // pointer
int* pn2 = &*pn; // pn2 == pn
int A::* mp = &A::i; // pointer to data member
void (B::*mpf)() = &B::f; // pointer to member function
void (*pf)(int) = &f; // overload resolution due to initialization context
// auto pf2 = &f; // error: ambiguous overloaded function type
auto pf2 = static_cast<void (*)(int)>(&f); // overload resolution due to cast
}
Встроенные операторы доступа к членам
Выражения с оператором доступа к члену имеют вид
expr . template(необязательно) id-expr | (1) | |
expr -> template(необязательно) id-expr | (2) | |
expr . splice-specifier | (3) | (начиная с C++26) |
expr -> splice-specifier | (3) | (начиная с C++26) |
T, если только id-expr не обозначает псевдодеструктор (см. ниже).T*, если только id-expr не обозначает псевдодеструктор (см. ниже).id-expr — это имя (формально — выражение-идентификатор, обозначающее) члена T или однозначного и доступного базового класса B типа T (например, E1.E2 или E1->E2), возможно, квалифицированное (например, E1.B::E2 или E1->B::E2), а также, возможно, содержащее дизамбигуатор template (например, E1.template E2 или E1->template E2).
| splice-specifier — это splice-выражение, обозначающее член | (начиная с C++26) |
Если вызывается пользовательский operator->, к полученному значению рекурсивно снова применяется operator->, пока не будет достигнут operator->, возвращающий обычный указатель. После этого к этому указателю применяются встроенные правила.
Для встроенных типов выражение E1->E2 в точности эквивалентно (*E1).E2; поэтому следующие правила относятся только к E1.E2.
В выражении E1.E2:
E2 является статическим членом данных: - Если
E2имеет ссылочный типT&илиT&&(начиная с C++11), результатом является lvalue типаT, обозначающее объект или функцию, на которые привязана ссылка. - В противном случае, если тип
E2обозначить какT, результатом является lvalue типаT, обозначающее этот статический член данных.
E1 вычисляется, а результат отбрасывается.E2 является нестатическим членом данных: - Если
E2имеет ссылочный типT&илиT&&(начиная с C++11), результатом является lvalue типаT, обозначающее объект или функцию, на которые привязан соответствующий ссылочный членE1. - В противном случае, если
E1является lvalue, результатом является lvalue, обозначающее этот нестатический член данныхE1. - В противном случае (если
E1является rvalue(до C++17 включительно)xvalue (которое может быть материализовано из пр-значения)(начиная с C++17)), результатом является rvalue(до C++11 включительно)xvalue(начиная с C++11), обозначающее этот нестатический член данныхE1.
E2 не является членом mutable, cv-квалификация результата является объединением cv-квалификаций E1 и E2; в противном случае (если E2 является членом mutable) она является объединением volatile-квалификаций E1 и E2.E2 является набором перегрузок (содержащим одну или несколько статических функций-членов и нестатических функций-членов), E1.E2 должен быть (возможно, заключённым в скобки) левым операндом оператора вызова функции-члена, а для выбора функции, к которой относится E2, используется разрешение перегрузки функций. После этого: - Если
E2является статической функцией-членом, результатом является lvalue, обозначающее эту статическую функцию-член. В этом случаеE1вычисляется, а результат отбрасывается. - В противном случае (
E2является нестатической функцией-членом) результатом является пр-значение, обозначающее эту нестатическую функцию-членE1.
E2 является элементом перечисления-члена, а тип E2 обозначить как T, результатом является rvalue(до C++11 включительно)пр-значение(начиная с C++11) типа T, значение которого совпадает со значением элемента перечисления. 6) Если E2 обозначает отношение прямого базового класса, результат обозначает соответствующий подобъект прямого базового класса в E1. struct B { int b; };
struct C : B { int get() const { return b; } };
struct D : B, C {};
constexpr int f() {
D d = {1, {}};
constexpr auto ctx = std::meta::access_context::current();
// b unambiguously refers to the direct base class of type B,
// not the indirect base class of type B
B& b = d.[: std::meta::bases_of(^^D, ctx)[0] :];
b.b += 10;
return 10 * b.b + d.get();
}
static_assert(f() == 110);
| (начиная с C++26) |
E1 имеет тип ScalarType, а E2 — это ~, за которым следует имя типа или спецификатор decltype, обозначающий тот же тип (без учёта cv-квалификаторов), возможно, квалифицированный, результатом является особый вид пр-значения, который можно использовать только как левый операнд оператора вызова функции и ни для каких других целей.void, вычисляет E1 и завершает время жизни своего результирующего объекта. Это единственный случай, когда левый операнд оператора . имеет неклассовый тип. Возможность вызова псевдодеструктора позволяет писать код, не выясняя, существует ли деструктор для заданного типа.operator. нельзя перегружать, а для operator-> при разрешении перегрузки для пользовательских операторов встроенный оператор не добавляет никаких дополнительных сигнатур функций: встроенный operator-> не применяется, если существует перегруженный operator->, являющийся подходящей функцией.
#include <cassert>
#include <iostream>
#include <memory>
struct P
{
template<typename T>
static T* ptr() { return new T; }
};
template<typename T>
struct A
{
A(int n): n(n) {}
int n;
static int sn;
int f() { return 10 + n; }
static int sf() { return 4; }
class B {};
enum E {RED = 1, BLUE = 2};
void g()
{
typedef int U;
// keyword template needed for a dependent template member
int* p = T().template ptr<U>();
p->~U(); // U is int, calls int's pseudo destructor
delete p;
}
};
template<>
int A<P>::sn = 2;
struct UPtrWrapper
{
std::unique_ptr<std::string> uPtr;
std::unique_ptr<std::string>& operator->() { return uPtr; }
};
int main()
{
A<P> a(1);
std::cout << a.n << ' '
<< a.sn << ' ' // A::sn also works
<< a.f() << ' '
<< a.sf() << ' ' // A::sf() also works
// << &a.f << ' ' // error: ill-formed if a.f is not the
// left-hand operand of operator()
// << a.B << ' ' // error: nested type not allowed
<< a.RED << ' '; // enumerator
UPtrWrapper uPtrWrap{std::make_unique<std::string>("wrapped")};
assert(uPtrWrap->data() == uPtrWrap.operator->().operator->()->data());
}
Вывод:
1 2 11 4 1
Если E2 является нестатическим членом или отношением прямого базового класса(начиная с C++26), а результат E1 — это объект, тип которого не подобен типу E1, поведение не определено:
struct A { int i; };
struct B { int j; };
struct D : A, B {};
void f()
{
D d;
static_cast<B&>(d).j; // OK, object expression designates the B subobject of d
reinterpret_cast<B&>(d).j; // undefined behavior
} Встроенные операторы доступа через указатель на член
Выражения доступа к члену через указатели на члены имеют вид
lhs .*rhs | (1) | |
lhs ->*rhs | (2) |
T.T*.rhs должно быть rvalue типа «указатель на член» (данных или функцию) типа T либо указателем на член однозначного и доступного базового класса B типа T.
Для встроенных типов выражение E1->*E2 в точности эквивалентно (*E1).*E2; поэтому следующие правила относятся только к E1.*E2.
В выражении E1.*E2:
E2 является указателем на член данных, - если
E1является lvalue, результатом является lvalue, обозначающее этот член данных; - в противном случае (если
E1является rvalue(до C++17 включительно)xvalue (которое может быть материализовано из пр-значения)(начиная с C++17)), результатом является rvalue(до C++11 включительно)xvalue(начиная с C++11), обозначающее этот член данных;
E2 является указателем на функцию-член, результатом является особый вид пр-значения, обозначающий эту функцию-член, который можно использовать только как левый операнд оператора вызова функции-члена и ни для каких других целей;E2 является нулевым значением указателя на член, поведение не определено;E1 является объектом, тип которого не подобен типу E1, либо его наиболее производный объект не содержит член, на который ссылается E2, поведение не определено;E1 является rvalue, а E2 указывает на функцию-член с ref-квалификатором &, программа некорректна если только функция-член не имеет cv-квалификатор const, но не volatile(начиная с C++20); 7) если E1 является lvalue, а E2 указывает на функцию-член с ref-квалификатором &&, программа некорректна. | (начиная с C++11) |
При разрешении перегрузки для определённых пользователем операторов для каждой комбинации типов D, B, R, где тип класса B является либо тем же классом, что и D, либо однозначным и доступным базовым классом D, а R является типом объекта или функции, в разрешении перегрузки участвует следующая сигнатура функции:
R& operator->*(D*, R B::*); |
Оба операнда могут иметь cv-квалификаторы; в этом случае cv-квалификация возвращаемого типа представляет собой объединение cv-квалификаций операндов.
#include <iostream>
struct S
{
S(int n) : mi(n) {}
mutable int mi;
int f(int n) { return mi + n; }
};
struct D : public S
{
D(int n) : S(n) {}
};
int main()
{
int S::* pmi = &S::mi;
int (S::* pf)(int) = &S::f;
const S s(7);
// s.*pmi = 10; // error: cannot modify through mutable
std::cout << s.*pmi << '\n';
D d(7); // base pointers work with derived object
D* pd = &d;
std::cout << (d.*pf)(7) << ' '
<< (pd->*pf)(8) << '\n';
} Вывод:
7 14 15
Стандартная библиотека
Оператор индексирования перегружен во многих классах стандартных контейнеров:
| доступ к указанному биту (открытая функция-член std::bitset<N>) |
|
| индексированный доступ к управляемому массиву (открытая функция-член std::unique_ptr<T,Deleter>) |
|
| доступ к указанному символу (открытая функция-член std::basic_string<CharT,Traits,Allocator>) |
|
| доступ к указанному элементу (открытая функция-член std::array<T,N>) |
|
| доступ к указанному элементу (открытая функция-член std::deque<T,Allocator>) |
|
| доступ к указанному элементу (открытая функция-член std::vector<T,Allocator>) |
|
| доступ к указанному элементу или его вставка (открытая функция-член std::map<Key,T,Compare,Allocator>) |
|
| доступ к указанному элементу или его вставка (открытая функция-член std::unordered_map<Key,T,Hash,KeyEqual,Allocator>) |
|
| доступ к элементу по индексу (открытая функция-член std::reverse_iterator<Iter>) |
|
| доступ к элементу по индексу (открытая функция-член std::move_iterator<Iter>) |
|
| получение/задание элемента, среза или маски valarray (открытая функция-член std::valarray<T>) |
|
| возвращает указанное подсовпадение (открытая функция-член std::match_results<BidirIt,Alloc>) |
Операторы разыменования и доступа к членам перегружены во многих классах итераторов и умных указателей:
| разыменовывает указатель на управляемый объект (открытая функция-член std::unique_ptr<T,Deleter>) |
|
| разыменовывает сохранённый указатель (открытая функция-член std::shared_ptr<T>) |
|
| обеспечивает доступ к управляемому объекту (открытая функция-член std::auto_ptr<T>) |
|
| разыменовывает итератор (открытая функция-член std::raw_storage_iterator<OutputIt,T>) |
|
| разыменовывает уменьшенный базовый итератор (открытая функция-член std::reverse_iterator<Iter>) |
|
| ничего не делает (открытая функция-член std::back_insert_iterator<Container>) |
|
| ничего не делает (открытая функция-член std::front_insert_iterator<Container>) |
|
| ничего не делает (открытая функция-член std::insert_iterator<Container>) |
|
| обеспечивает доступ к элементу, на который указывает итератор (открытая функция-член std::move_iterator<Iter>) |
|
| возвращает текущий элемент (открытая функция-член std::istream_iterator<T,CharT,Traits,Distance>) |
|
| ничего не делает (открытая функция-член std::ostream_iterator<T,CharT,Traits>) |
|
| получает копию текущего символа (открытая функция-член std::istreambuf_iterator<CharT,Traits>) |
|
| ничего не делает (открытая функция-член std::ostreambuf_iterator<CharT,Traits>) |
|
| обеспечивает доступ к текущему совпадению (открытая функция-член std::regex_iterator<BidirIt,CharT,Traits>) |
|
| обеспечивает доступ к текущему подсовпадению (открытая функция-член std::regex_token_iterator<BidirIt,CharT,Traits>) |
Ни один класс стандартной библиотеки не перегружает operator&. Наиболее известный пример перегруженного operator& — класс Microsoft COM CComPtr, хотя этот оператор также встречается в EDSL, таких как boost.spirit.
Ни один класс стандартной библиотеки не перегружает operator->*. Предлагалось использовать его как часть интерфейса умного указателя; и действительно, эту роль он выполняет у акторов в boost.phoenix, однако чаще он встречается в EDSL, таких как cpp.react.
Примечания
| Макрос проверки поддержки возможностей | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_multidimensional_subscript | 202110L | (C++23) | Многомерный оператор индексирования |
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были ретроактивно применены к ранее опубликованным стандартам C++.
| DR | Применён к | Опубликованное поведение | Корректное поведение |
|---|---|---|---|
| CWG 1213 | C++11 | индексирование rvalue-массива приводило к lvalue | переклассифицировано как xvalue |
| CWG 1458 | C++98 | применение & к lvalue неполного типа класса, которыйобъявляет operator&, приводило к неопределённому поведению | не указано, какой & используется |
| CWG 1642 | C++98 | правый операнд rhs встроенных операторов доступа через указатель на член мог быть lvalue | может быть только rvalue |
| CWG 1800 | C++98 | при применении & к нестатическому члену данныханонимного объединения-члена было неясно, учитывается ли анонимное объединение в типе результата | анонимное объединение не включается в тип результата |
| CWG 2614 | C++98 | результат E1.E2 был неясен, если E2 является членом-ссылкой или перечислителем | прояснено |
| CWG 2725 | C++98 | если E2 является статической функцией-членом, E1.E2 корректнодаже если это не левый операнд operator() |
E1.E2 некорректнов этом случае |
| CWG 2748 | C++98 | поведение E1->E2 было неясным, если E1 являетсянулевым указателем, а E2 ссылается на статический член | в этом случае поведение не определено |
| CWG 2813 | C++98 |
E1 не являлось выражением с отбрасываемым значением, еслиE1.E2 обозначает статический член или перечисление | является |
| CWG 2823 | C++98 | поведение *expr было неясным, если exprне указывает на объект или функцию | прояснено |
См. также
| Основные операторы | ||||||
|---|---|---|---|---|---|---|
| Присваивание |
Инкремент, декремент | Арифметические | Логические | Сравнение |
Доступ к членам | Прочие |
a = b a += b a -= b a *= b a /= b a %= b a &= b a |= b a ^= b a <<= b a >>= b | ++a --a a++ a-- | +a -a a + b a - b a * b a / b a % b ~a a & b a | b a ^ b a << b a >> b | !a a && b a || b | a == b a != b a < b a > b a <= b a >= b a <=> b | a[...] *a &a a->b a.b a->*b a.*b | вызов функцииa(...) |
запятаяa, b |
||||||
условный операторa ? b : c |
||||||
| Специальные операторы | ||||||
|
|
||||||
| Документация по C для Операторы доступа к членам |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/operator_member_access