нельзя ввести новый символ для оператора нельзя поменять приоритет операторов нельзя поменять порядок вычисления операторов большинство унарных операторов правоассоциативны, а бинарных - левоассоциативны это всё зашито в язык

copy&swap идиома:

Quat& Quat::operator= (Quat other) & {
    swap(other);
    return *this;
}

чтобы оператор присваивания у объектов работал только для lvalue, нужно добавить в конце &. чтобы только для rvalue: &&

Return Value Optimization компилятор умеет возвращать локальную переменную по значению, если возвращать локальную переменную без экспрешенов частный случай copy elision

Quat Quat::operator+ (const Quat& rhs) const {
    Quat copy = *this;
    copy += rhs;
    return copy;
}
//в случаях ниже 2 копирования, вместо 1
Quat Quat::operator+ (const Quat& rhs) const {
    Quat copy = *this;
    return copy += rhs; //лишнее копирование
}
Quat Quat::operator+ (const Quat& rhs) const {
    return *this += rhs; //лишнее копирование
}

оператор вывода в поток определяется вне класса, потому что у него левый аргумент это поток (по ссылке), а правый - объект (по const ссылке). возвращаемый тип: ссылка на поток Если бы он был членом класса, его левый операнд (объект потока) должен был бы быть членом этого класса, что не соответствует логике оператора вывода можно определить внутри класса пометив как friend. это будет как-бы внешняя функция, но имеющая доступ к приватным полям (inline friend function)

перегружать <, <=, >= через < и в одну строчку, тогда будет максимальная оптимизация

Three-way comparison или космический корабль (С++20) нету у примитивных типов возвращаемый тип отношения порядка weak и strong ordering компилятор может сам сгенерировать. имеют 3 возможных значения: less, equal, greater (в partial_ordering ещё unordered) strong: строгий порядок, соблюдается a = b -> f(a) = f(b), т.е. объекты не отличимы weak: строгий порядок, не соблюдается a = b -> f(a) = f(b), т.е. могут быть отличия если определить такой оператор по умолчанию, то автоматически определятся 4 оператора большеменьше, а также равенство и неравенство лексикографически для объекта класса симметрично (т.е. доопределяются 12 операторов). если же самому определить оператор spaceship, то == и != недопределяется, потому что не эффективно, так решил комитет. например, в случае со строками, сначала нужно проверить size работает оптимизированнее у std::string вообще delete все операторы сравнения и есть только spaceship там weak_ordering, тк строки могут быть равны, если у них разный capacity

если определить дефолтное == (поэлементное сравнение полей), то тогда != доопределится

префиксный оператор быстрее постфиксного потому что префиксный делает копию за О(n)

в std::sort в качестве 3 параметра компоратора можно засунуть как сишный указатель на функцию, так и объект класса/структуры с определённым оператором () для нужных типов, который возвращает bool потому что для сортировки синтаксически делается подстановка, вызываются скобочки от переменной. и неважно это функция или объект, у которого есть круглые скобочки. такие классы называются функциональными классами или функторами, а объекты - функциональными объектами. преимущество над сишными функциями: можно иметь поля и использовать их в сравнении

template<typename T>
struct less {
	constexpr bool operator() (const T& lhs, const T& rhs) const { return lhs < rhs; }
}

std::less - функциональный класс, в котором перегружен оператор () от двух const T& и возвращает T1 < T2

значения … :

  • переменное число аргументов в сишных функциях
  • распаковка пакетов шаблонных параметров и fold expressions
  • в catch

.* - указатель на поле или метод (нельзя переопределить, как и оператор .) ->* указатель на поле или метод объекта, который указатель (можно переопределить)

перегрузка new и delete

оператор new состоит из 2 частей - функция с названием operator new, которая ответственна за выделение памяти и вторая - вызов конструкторов на выделенной памяти. перегрузить можно только функцию оператор new. у placement new отсутствует первая часть. отдельно есть функция operator new[] и operator delete[]. их тоже можно переопределить

можно перегрузить placement new. если перегрузить new, то placement new не перегрузится

почему оператор new можно переопределить с такой же сигнатурой, хотя в этой же области видимости он определен стандартной библиотекой и это не нарушает ODR? есть специальный вид функций, они помечены макросом _LIBCPP_WEAK (слабые символы), которые при переопределении замещаются, а не кидают ошибку линкера

почему вектор вызывает оператор new, а не оператор new[]? потому что в аллокаторе написано return operator new(count*sizeof(T)) - сделать только первую часть оператор new

delete[] - сначала вызываются деструкторы в нужном количестве, а потом по этому указателю вызывается функция delete[] когда мы выделяем нестандартные типы (типы, у которых существуют нетривиальные деструкторы), оператору new[] нужно записать, сколько их было, чтобы delete[] знал, сколько деструкторов вызвать. это число хранится перед началом выделенной памяти для объектов в delete[] делается неявный сдвиг влево на 8 байт, если тип был нетривиальный

существует версия оператора new, которая не бросает исключения, а просто возвращает nullptr, если не удалось выделить память. для этого в параметры new подается объект nothrow структуры nothrow_t int* p = new(std::nothrow) int[10]

параметры operator new:

  • size_t
  • `const std::nothrow_t& tag
  • произвольные параметры (можно определить функцию operator new с дополнительными параметрами) int* p = new(1, 3.14) int(5). каждому кастомному new нужен кастомные delete c таким же набором параметров, иначе UB. но у delete нет вызова с кастомными параметрами, поэтому operator delete(p, 1, 3.3);

если в динамической памяти с помощью оператора new создается объект и конструктор этого объекта кидает исключение, то стандарт гарантирует, что под капотом будет вызван operator delete на этом же адресе и от таких же параметров, и только после этого исключение полетит. даже, если на 5 объекте произошло исключение, то все созданные до него объекты также удалятся

Base* b = new Derived; delete b; - UB, если деструкторы не виртуальные

Base* b = new Derived[10]; `delete[] b; - UB, даже если деструкторы виртуальные, тк он не понимает, куда дальше сдвигаться при удалении (мб Base не уничтожит)

можно доопределить operator new для своих типов прям в классе. они статические по умолчанию. тогда именно при выделении этих объектов будет вызываться именно этот operator new

можно вызвать функцию std::set_new_handler и передать ему указатель на функцию. и тогда эта функция будет вызываться при вызове стандартного new в случае, если у него не удалось выделить память

как запретить создание объекта на куче, а разрешить только на стеке? определяем для типа operator new, который будет = delete();

как запретить создание объекта на стеке, чтобы можно было создавать только на куче?

  • сделать конструкторы приватными и сделать так, чтобы их мог вызывать только метод другого класса на куче
  • сделать деструктор приватным. тогда его будет нельзя создавать на стеке. тк создание объекта на стеке подразумевает автоматическую возможность вызвать деструктор из него. но нельзя будет вызвать обычный delete. в C++ был добавлен специальный оператор для решения этой проблемы - destroying deallocation functions это оператор delete с доп. параметром std::destroying_delete_t (тэг). это функция, которая будучи вызванной, убирает автоматический вызов деструктора при вызове delete. придётся самомим делать p->~T (крч определяем delete с такими параметрами для нашего класса и можно будет делать delete из мейна, хотя деструктор приватный)