базовые концепции ООП — перегрузка операторовbaseline OOP concepts — operator overloading

ИндексаторыIndexers

В C++ нет отдельного языкового понятия «индексатор», как в C#/Python — есть перегрузка operator[], обычного метода класса с особым синтаксисом вызова. Пара const/неconst перегрузок — стандартный паттерн, дающий контейнеру и безопасное чтение через константную ссылку, и изменение через обычную.C++ has no distinct language concept of an "indexer" like C#/Python does — it has operator[] overloading, an ordinary class method with special call syntax. A const/non-const overload pair is the standard pattern, giving a container both safe read access through a const reference and mutation through a plain one.

operator[]

ОпределениеDefinition

operator[] — перегружаемый оператор, позволяющий использовать синтаксис obj[key] вместо явного вызова метода вроде obj.at(key). Это не специальный языковой механизм для контейнеров, а такая же перегрузка оператора, как operator+ или operator== — компилятор просто транслирует obj[key] в obj.operator[](key).operator[] is an overloadable operator that lets you write obj[key] instead of explicitly calling a method like obj.at(key). It's not a special container-specific language mechanism — it's operator overloading just like operator+ or operator==; the compiler simply translates obj[key] into obj.operator[](key).

class SparseArray {
    std::unordered_map<size_t, double> data;
public:
    double& operator[](size_t i) { return data[i]; }   // obj[5] = 3.14; работает как обычное присваивание в ссылку// obj[5] = 3.14; works like an ordinary assignment through a reference
};

SparseArray arr;
arr[5] = 3.14;   // на деле — arr.operator[](5) = 3.14;// really — arr.operator[](5) = 3.14;

Const и неconst версииConst and non-const versions

Стандартная библиотека почти везде объявляет operator[] дважды — const-версию, возвращающую const T& (запрещая изменение через неё), и неconst-версию, возвращающую T& (разрешая изменение). Компилятор выбирает нужную по той же логике, что и для любого другого перегруженного по const метода — в зависимости от того, является ли сам объект константным.The standard library almost everywhere declares operator[] twice — a const version returning const T& (forbidding modification through it) and a non-const version returning T& (allowing modification). The compiler picks the right one using the same logic as for any other method overloaded on const — based on whether the object itself is const.

class Matrix {
    std::vector<double> data;
public:
    double& operator[](size_t i) { return data[i]; }               // для неconst Matrix — можно изменять// for a non-const Matrix — mutation allowed
    const double& operator[](size_t i) const { return data[i]; }   // для const Matrix — только чтение// for a const Matrix — read-only
};

void scale(Matrix& m)             { m[0] *= 2.0; }        // вызовется неconst версия// the non-const version runs
void print(const Matrix& m)     { std::cout << m[0]; }   // вызовется const версия// the const version runs

Многомерная индексация (C++23)Multi-dimensional subscripting (C++23)

До C++23 operator[] принимал ровно один параметр — многомерные контейнеры (матрицы) были вынуждены либо перегружать operator() для нескольких индексов, либо предоставлять цепочку [][] через промежуточный прокси-объект. C++23 разрешил operator[] принимать несколько параметров через запятую напрямую.Before C++23, operator[] took exactly one parameter — multi-dimensional containers (matrices) had to either overload operator() for multiple indices, or provide a [][] chain through an intermediate proxy object. C++23 allows operator[] to take several comma-separated parameters directly.

class Grid {
    std::vector<double> data;
    size_t cols;
public:
    double& operator[](size_t row, size_t col) {   // C++23 — два параметра сразу// C++23 — two parameters directly
        return data[row * cols + col];
    }
};
grid[2, 3] = 1.0;   // естественный синтаксис вместо grid(2, 3) или grid[2][3]// natural syntax instead of grid(2, 3) or grid[2][3]

Библиотечный std::mdspan (тоже C++23) — прямой практический потребитель этой фичи, обеспечивающий невладеющий многомерный вид на непрерывный блок памяти с многоаргументным operator[].The library's std::mdspan (also C++23) is the direct practical consumer of this feature, providing a non-owning multi-dimensional view over a contiguous memory block with a multi-argument operator[].

ГраблиGotchas

operator[] у std::map создаёт элемент, если ключа нетon std::map inserts an element if the key is missing

В отличие от at(), бросающего исключение на отсутствующем ключе, неconst operator[] у std::map/std::unordered_map молча вставляет новый элемент со значением по умолчанию, если ключа ещё нет — простая проверка if (m[key] == …) способна незаметно раздуть map лишними записями.Unlike at(), which throws on a missing key, the non-const operator[] on std::map/std::unordered_map silently inserts a new default-valued element if the key doesn't exist yet — a plain if (m[key] == …) check can silently bloat the map with unwanted entries.

Отсутствие проверки границ по умолчаниюNo bounds checking by default

std::vector::operator[] не проверяет выход индекса за границы — это undefined behavior, а не исключение. Проверенная альтернатива — метод .at(), бросающий std::out_of_range, но с дополнительными накладными расходами на саму проверку на каждом обращении.std::vector::operator[] doesn't check whether the index is out of bounds — that's undefined behavior, not an exception. The checked alternative is the .at() method, which throws std::out_of_range, at the cost of the bounds check itself running on every access.

Возврат по значению вместо ссылки ломает идиому изменения через индексациюReturning by value instead of by reference breaks the mutate-through-subscript idiom

Если operator[] возвращает T вместо T&, выражение arr[i] = value; либо не скомпилируется (нельзя присвоить временному значению), либо, что хуже, скомпилируется и молча изменит временную копию, а не элемент внутри контейнера.If operator[] returns T instead of T&, the expression arr[i] = value; either won't compile (you can't assign to a temporary) or, worse, compiles fine and silently mutates the temporary copy rather than the element actually inside the container.