базовые концепции ООП — перегрузка операторовbaseline OOP concepts — operator overloading
В 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[] — перегружаемый оператор, позволяющий использовать синтаксис 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;
Стандартная библиотека почти везде объявляет 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 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[].
В отличие от 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.
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.
Если 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.