Каковы основные правила и идиомы для перегрузки операторов? - PullRequest
2019 голосов
/ 12 декабря 2010

Примечание: ответы были даны в определенном порядке , но поскольку многие пользователи сортируют ответы по голосам, а не по времени, которое они получили, вот индекс ответов в том порядке, в котором они имеют наибольшее значение:

(Примечание. Предполагается, что это будет вход в FAQ по C ++ для Stack Overflow . Если вы хотите критиковать идею предоставления FAQ в этой форме, тогда публикация по мета, которая началась все это будет местом для этого. Ответы на этот вопрос отслеживаются в C ++ чате , где идея FAQ возникла в первую очередь, поэтому ваш ответ, скорее всего, будет прочитан теми, кто придумал эту идею.)

Ответы [ 7 ]

984 голосов
/ 12 декабря 2010

Общие операторы для перегрузки

Большая часть работы в операторах перегрузки - код котельной плиты.Это неудивительно, поскольку операторы являются просто синтаксическим сахаром, их фактическая работа может выполняться (и часто направляется) простыми функциями.Но важно, чтобы вы правильно поняли этот код.Если вы потерпите неудачу, либо код вашего оператора не скомпилируется, либо код вашего пользователя не скомпилируется, либо код вашего пользователя будет вести себя удивительно.

Оператор присваивания

Можно много чего сказатьо назначении.Тем не менее, большинство из них уже было сказано в знаменитом FAQ по копированию и замене GMan , поэтому я пропущу большую часть этого здесь, только перечисляя идеальный оператор присваивания для справки:

X& X::operator=(X rhs)
{
  swap(rhs);
  return *this;
}

Операторы битового смещения (используются для потокового ввода / вывода)

Операторы битового смещения << и >>, хотя и по-прежнему используются в аппаратном интерфейсе для функций управления битами, которые они наследуют от C, стали болеепреобладает в качестве перегруженных потоковых операторов ввода и вывода в большинстве приложений.Для перегрузки руководства в качестве операторов манипуляции битами см. Раздел ниже о двоичных арифметических операторах.Для реализации вашего собственного пользовательского формата и логики синтаксического анализа, когда ваш объект используется с iostreams, продолжайте.

Операторы потока, среди наиболее часто перегруженных операторов, являются бинарными инфиксными операторами, для которых синтаксис не устанавливает никаких ограничений относительно того,должны быть членами или не членами.Поскольку они изменяют свой левый аргумент (они изменяют состояние потока), они должны, согласно практическим правилам, быть реализованы как члены типа их левого операнда.Однако их левые операнды являются потоками из стандартной библиотеки, и хотя большинство операторов вывода и ввода потока, определенных стандартной библиотекой, действительно определены как члены классов потоков, при реализации операций вывода и ввода для ваших собственных типов выне может изменить типы потоков стандартной библиотеки.Вот почему вам нужно реализовать эти операторы для ваших собственных типов как функции, не являющиеся членами.Вот две канонические формы:

std::ostream& operator<<(std::ostream& os, const T& obj)
{
  // write obj to stream

  return os;
}

std::istream& operator>>(std::istream& is, T& obj)
{
  // read obj from stream

  if( /* no valid object of T found in stream */ )
    is.setstate(std::ios::failbit);

  return is;
}

При реализации operator>> ручная установка состояния потока необходима только тогда, когда считывание завершилось успешно, но результат не соответствует ожидаемому.

Оператор вызова функции

Оператор вызова функции, используемый для создания объектов функций, также известных как функторы, должен быть определен как член функция, поэтомуу него всегда есть неявный аргумент this функций-членов.Кроме этого, он может быть перегружен, чтобы принимать любое количество дополнительных аргументов, включая ноль.

Вот пример синтаксиса:

class foo {
public:
    // Overloaded call operator
    int operator()(const std::string& y) {
        // ...
    }
};

Использование:

foo f;
int a = f("hello");

Во всей стандартной библиотеке C ++ объекты функций всегда копируются.Поэтому ваши собственные функциональные объекты должны быть дешевыми для копирования.Если функциональному объекту абсолютно необходимо использовать данные, которые являются дорогостоящими для копирования, лучше хранить эти данные в другом месте и обращаться к ним с помощью функционального объекта.

Операторы сравнения

Сравнение двоичного инфиксаоператоры должны, согласно практическим правилам, быть реализованы как функции, не являющиеся членами 1 .Отрицание унарного префикса ! должно (согласно тем же правилам) быть реализовано как функция-член.(но обычно не рекомендуется перегружать его.)

Алгоритмы стандартной библиотеки (например, std::sort()) и типы (например, std::map) всегда ожидают присутствия operator<.Однако пользователи вашего типа будут ожидать, что все остальные операторы будут присутствовать также , поэтому, если вы определите operator<, обязательно следуйте третьему фундаментальному правилу перегрузки операторов, а также определите все остальныеоператоры логического сравнения.Канонический способ их реализации заключается в следующем:

inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return  operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}

ВажныйЗдесь следует отметить, что только два из этих операторов на самом деле что-то делают, другие просто пересылают свои аргументы любому из этих двух, чтобы выполнить реальную работу.

Синтаксис для перегрузки оставшихся двоичных логических операторов (||, &&) следует правилам операторов сравнения.Тем не менее, очень маловероятно, что вы найдете разумный вариант использования этих 2 .

1 Как и со всеми правиламибольшого пальца, иногда могут быть причины, чтобы сломать этот тоже.Если это так, не забывайте, что левый операнд бинарных операторов сравнения, который для функций-членов будет *this, также должен быть const.Поэтому оператор сравнения, реализованный как функция-член, должен иметь такую ​​подпись:

bool operator<(const X& rhs) const { /* do actual comparison with *this */ }

(обратите внимание на const в конце.)

2 Следует отметить, что во встроенной версии || и && используется семантика ярлыков.В то время как пользовательские (потому что они являются синтаксическим сахаром для вызовов методов) не используют сокращенную семантику.Пользователь будет ожидать, что эти операторы будут иметь сокращенную семантику, и их код может зависеть от нее, поэтому настоятельно рекомендуется НИКОГДА не определять их.

Арифметические операторы

Унарные арифметические операторы

Унарные операторы увеличения и уменьшения могут быть как префиксными, так и постфиксными.Чтобы отличить одно от другого, варианты postfix принимают дополнительный фиктивный аргумент int.Если вы перегружаете инкремент или декремент, убедитесь, что вы всегда используете как префиксную, так и постфиксную версии.Вот каноническая реализация инкремента, декремент следует тем же правилам:

class X {
  X& operator++()
  {
    // do actual increment
    return *this;
  }
  X operator++(int)
  {
    X tmp(*this);
    operator++();
    return tmp;
  }
};

Обратите внимание, что постфиксный вариант реализован в терминах префикса.Также обратите внимание, что postfix делает дополнительную копию. 2

Перегрузка унарных минусов и плюсов не очень распространена и, вероятно, ее лучше избегать.При необходимости они, вероятно, должны быть перегружены как функции-члены.

2 Также обратите внимание, что вариант с постфиксом делает больше работы и поэтому менее эффективен в использовании, чем вариант с префиксом.Это хорошая причина, как правило, предпочитать увеличение префикса над увеличением постфикса.Хотя компиляторы обычно могут оптимизировать дополнительную работу приращения постфикса для встроенных типов, они могут быть не в состоянии сделать то же самое для пользовательских типов (которые могут выглядеть невинно, как итератор списка).Как только вы привыкли делать i++, становится очень трудно запомнить ++i вместо этого, когда i не является встроенным типом (плюс вам придется менять код при смене типа), поэтомулучше использовать привычку всегда использовать приращение префикса, если только постфикс не требуется явно.

Двоичные арифметические операторы

Для двоичных арифметических операторов не забывайте соблюдать третийперегрузка оператора основного правила: если вы предоставляете +, также укажите +=, если вы предоставляете -, не пропускайте -= и т. д. Эндрю Кениг, как говорят, первым заметил, что составные операторы присваиваниямогут быть использованы в качестве основы для своих несоставных аналогов.То есть оператор + реализован в терминах +=, - реализован в терминах -= и т. Д.

Согласно нашим практическим правилам, + и его компаньоны должны бытьне-члены, в то время как их составные аналоги присваивания (+= и т. д.), изменяя свой левый аргумент, должны быть членами.Вот примерный код для += и +;другие двоичные арифметические операторы должны быть реализованы таким же образом:

class X {
  X& operator+=(const X& rhs)
  {
    // actual addition of rhs to *this
    return *this;
  }
};
inline X operator+(X lhs, const X& rhs)
{
  lhs += rhs;
  return lhs;
}

operator+= возвращает свой результат по ссылке, а operator+ возвращает копию своего результата.Конечно, возврат ссылки обычно более эффективен, чем возврат копии, но в случае operator+ нет способа обойти копирование.Когда вы пишете a + b, вы ожидаете, что результатом будет новое значение, поэтому operator+ должно возвращать новое значение. 3 Также обратите внимание, что operator+ принимает свой левый операнд по копии , а не по константной ссылке.Причиной этого является то же, что и причина, по которой operator= получает свой аргумент за копию.

Операторы битовых манипуляций ~ & | ^ << >> должныбыть реализован так же, как арифметические операторы.Однако (за исключением перегрузки << и >> для вывода и ввода) существует очень мало разумных вариантов их использования.

3 Опять же, урокОтсюда следует, что a += b, в целом, более эффективен, чем a + b, и его следует, если возможно, предпочтительнее.

Подписка на массив

Оператор индекса массива являетсябинарный оператор, который должен быть реализован как член класса.Он используется для контейнероподобных типов, которые позволяют доступ к их элементам данных по ключу.Каноническая форма предоставления этого такова:

class X {
        value_type& operator[](index_type idx);
  const value_type& operator[](index_type idx) const;
  // ...
};

Если вы не хотите, чтобы пользователи вашего класса могли изменять элементы данных, возвращаемые operator[] (в этом случае вы можете опустить неконстантныйвариант), вы всегда должны предоставлять оба варианта оператора.

Если известно, что значение_типа ссылается на встроенный тип, вариант оператора const должен лучше возвращать копию, а не ссылку на const:

class X {
  value_type& operator[](index_type idx);
  value_type  operator[](index_type idx) const;
  // ...
};

Операторы для типов, похожих на указатели

Для определения собственных итераторов или умных указателей необходимо перегрузить оператор разыменования унарного префикса * и оператор доступа к члену двоичного инфиксного указателя ->:

class my_ptr {
        value_type& operator*();
  const value_type& operator*() const;
        value_type* operator->();
  const value_type* operator->() const;
};

Обратите внимание, что для них почти всегда потребуется как постоянная, так и неконстантная версия.Для оператора ->, если value_type имеет тип class (или struct или union), другой operator->() вызывается рекурсивно, пока operator->() не вернет значение не-классового типа.

Унарный оператор адреса не должен быть перегружен.

Для operator->*() см. этот вопрос .Он редко используется и, следовательно, редко перегружен.Фактически, даже итераторы не перегружают его.


Перейдите к Операторы преобразования

472 голосов
/ 12 декабря 2010

Три основных правила перегрузки операторов в C ++

Когда речь идет о перегрузке операторов в C ++, существует три основных правила, которым вы должны следовать .Как и во всех таких правилах, действительно есть исключения.Иногда люди отклонялись от них, и в результате получился неплохой код, но таких положительных отклонений мало, и они далеко друг от друга.По крайней мере, 99 из 100 таких отклонений, которые я видел, были неоправданными.Тем не менее, это может быть и 999 из 1000. Так что вам лучше придерживаться следующих правил.

  1. Всякий раз, когда значение оператора не является явно ясным и бесспорным, его не следует перегружать. Вместо этого укажитефункция с правильно выбранным именем.
    По сути, первое и главное правило перегрузки операторов в самом его сердце гласит: Не делайте этого .Это может показаться странным, потому что многое известно о перегрузке операторов, и поэтому многие статьи, главы книг и другие тексты имеют дело со всем этим.Но, несмотря на это, казалось бы, очевидное свидетельство, есть только удивительно немного случаев, когда перегрузка оператора является уместной .Причина в том, что на самом деле трудно понять семантику, лежащую в основе применения оператора, если использование оператора в области приложения хорошо известно и не оспаривается.Вопреки распространенному мнению, это вряд ли когда-либо.

  2. Всегда придерживайтесь хорошо известной семантики оператора.
    C ++ не накладывает ограничений на семантику перегруженных операторов.Ваш компилятор с радостью примет код, который реализует двоичный оператор +, чтобы вычесть его правый операнд.Однако пользователи такого оператора никогда не будут подозревать, что выражение a + b вычтет a из b.Конечно, это предполагает, что семантика оператора в области приложения неоспорима.

  3. Всегда предоставлять все из набора связанных операций.
    Операторы связаны друг с другом и на другие операции.Если ваш тип поддерживает a + b, пользователи ожидают, что смогут звонить и a += b.Если он поддерживает приращение префикса ++a, они будут ожидать, что a++ также будет работать.Если они могут проверить, является ли a < b, они, скорее всего, также смогут проверить, является ли a > b.Если они могут копировать-конструировать ваш тип, они ожидают, что назначение также будет работать.


Перейти к Решение между членом и не членом .

250 голосов
/ 12 декабря 2010

Общий синтаксис перегрузки операторов в C ++

Вы не можете изменить значение операторов для встроенных типов в C ++, операторы могут быть перегружены только для пользовательских типов 1 . То есть, по крайней мере, один из операндов должен быть пользовательского типа. Как и в случае с другими перегруженными функциями, операторы могут быть перегружены для определенного набора параметров только один раз.

Не все операторы могут быть перегружены в C ++. Среди операторов, которые не могут быть перегружены: . :: sizeof typeid .* и единственный троичный оператор в C ++, ?:

Среди операторов, которые могут быть перегружены в C ++, это:

  • арифметические операторы: + - * / % и += -= *= /= %= (все двоичные инфиксы); + - (одинарный префикс); ++ -- (одинарный префикс и постфикс)
  • манипулирование битами: & | ^ << >> и &= |= ^= <<= >>= (все двоичные инфиксы); ~ (одинарный префикс)
  • булева алгебра: == != < > <= >= || && (все двоичные инфиксы); ! (одинарный префикс)
  • управление памятью: new new[] delete delete[]
  • операторы неявного преобразования
  • Разное: = [] -> ->* , (все двоичные инфиксы); * & (все унарные префиксы) () (вызов функции, n-арный инфикс)

Однако тот факт, что вы можете перегружать все это, не означает, что вы должны сделать это. См. Основные правила перегрузки операторов.

В C ++ операторы перегружены в виде функций со специальными именами . Как и в случае с другими функциями, перегруженные операторы обычно могут быть реализованы либо как функция-член типа их левого операнда , либо как функции, не являющиеся членами . Вольны ли вы выбирать или использовать один из них, зависит от нескольких критериев. 2 Унарный оператор @ 3 , применяемый к объекту x, вызывается либо как operator@(x) или как x.operator@(). Двоичный инфиксный оператор @, применяемый к объектам x и y, называется либо operator@(x,y), либо x.operator@(y). 4

Операторы, которые реализованы как функции, не являющиеся членами, иногда являются типом своего операнда.

1 Термин «пользовательский» может вводить в заблуждение. C ++ делает различие между встроенными типами и пользовательскими типами. К первым относятся, например, int, char и double; к последним относятся все типы struct, class, union и enum, включая типы из стандартной библиотеки, даже если они не определены пользователями как таковые.

2 Это рассматривается в более поздней части этого FAQ.

3 @ не является допустимым оператором в C ++, поэтому я использую его как заполнитель.

4 Единственный троичный оператор в C ++ не может быть перегружен, и единственный n-арный оператор всегда должен быть реализован как функция-член. Перейти к Три основных правила перегрузки операторов в C ++ .

231 голосов
/ 12 декабря 2010

Решение между членом и не членом

Двоичные операторы = (присваивание), [] (подписка на массив), -> (доступ к элементу), а также оператор n-ary () (вызов функции) всегда должны быть реализованы как функции-члены , потому что синтаксис языка требует их.

Другие операторы могут быть реализованы как члены или не члены. Некоторые из них, однако, обычно должны быть реализованы как функции, не являющиеся членами, потому что вы не можете изменить их левый операнд. Наиболее выдающимися из них являются операторы ввода и вывода << и >>, левые операнды которых являются потоковыми классами из стандартной библиотеки, которые нельзя изменить.

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

  1. Если это унарный оператор , реализуйте его как член .
  2. Если бинарный оператор обрабатывает оба операнда одинаково (он оставляет их без изменений), реализуйте этот оператор как не-член функция.
  3. Если бинарный оператор не обрабатывает оба своих операнда одинаково (обычно он меняет свой левый операнд), он может быть полезно сделать его функцией member типа его левого операнда, если он должен получить доступ к частным частям операнда.

Конечно, как и во всех эмпирических правилах, есть исключения. Если у вас есть тип

enum Month {Jan, Feb, ..., Nov, Dec}

и вы хотите перегрузить операторы увеличения и уменьшения для него, вы не можете сделать это как функции-члены, так как в C ++ типы enum не могут иметь функции-члены. Таким образом, вы должны перегрузить его как бесплатную функцию. И operator<() для шаблона класса, вложенного в шаблон класса, намного проще писать и читать, когда выполняется как функция-член, встроенная в определение класса. Но это действительно редкие исключения.

(Однако, , если вы делаете исключение, не забывайте о проблеме const -ness для операнда, который для функций-членов становится неявным аргументом this. Если оператор как функция, не являющаяся членом, получит свой самый левый аргумент в качестве ссылки const, тот же оператор, что и функция-член, должен иметь const в конце, чтобы сделать *this const ссылку.)


Перейти к Общие операторы для перегрузки .

157 голосов
/ 17 мая 2013

Операторы преобразования (также известные как пользовательские преобразования)

В C ++ вы можете создавать операторы преобразования, операторы, которые позволяют компилятору преобразовывать между вашими типами и другими определенными типами. Существует два типа операторов преобразования: явные и неявные.

Операторы неявного преобразования (C ++ 98 / C ++ 03 и C ++ 11)

Оператор неявного преобразования позволяет компилятору неявно преобразовывать (например, преобразование между int и long) значение пользовательского типа в некоторый другой тип.

Ниже приведен простой класс с оператором неявного преобразования:

class my_string {
public:
  operator const char*() const {return data_;} // This is the conversion operator
private:
  const char* data_;
};

Операторы неявного преобразования, такие как конструкторы с одним аргументом, являются определяемыми пользователем преобразованиями. Компиляторы будут предоставлять одно пользовательское преобразование при попытке сопоставить вызов перегруженной функции.

void f(const char*);

my_string str;
f(str); // same as f( str.operator const char*() )

Сначала это кажется очень полезным, но проблема в том, что неявное преобразование включается даже тогда, когда оно не ожидается. В следующем коде будет вызван void f(const char*), потому что my_string() не является lvalue , поэтому первое не совпадает:

void f(my_string&);
void f(const char*);

f(my_string());

Начинающие легко ошибаются, и даже опытные программисты на C ++ иногда удивляются, потому что компилятор выбирает перегрузку, которую они не подозревают. Эти проблемы могут быть смягчены явными операторами преобразования.

Операторы явного преобразования (C ++ 11)

В отличие от операторов неявного преобразования, операторы явного преобразования никогда не сработают, если вы этого не ожидаете. Ниже приведен простой класс с явным оператором преобразования:

class my_string {
public:
  explicit operator const char*() const {return data_;}
private:
  const char* data_;
};

Обратите внимание на explicit. Теперь, когда вы пытаетесь выполнить неожиданный код из операторов неявного преобразования, вы получаете ошибку компилятора:

prog.cpp: In function ‘int main()’:
prog.cpp:15:18: error: no matching function for call to ‘f(my_string)’
prog.cpp:15:18: note: candidates are:
prog.cpp:11:10: note: void f(my_string&)
prog.cpp:11:10: note:   no known conversion for argument 1 from ‘my_string’ to ‘my_string&’
prog.cpp:12:10: note: void f(const char*)
prog.cpp:12:10: note:   no known conversion for argument 1 from ‘my_string’ to ‘const char*’

Чтобы вызвать явный оператор приведения, вы должны использовать static_cast, приведение в стиле C или приведение в стиле конструктора (т.е. T(value)).

Однако есть одно исключение: компилятору разрешено неявно преобразовывать в bool. Кроме того, компилятору не разрешается делать другое неявное преобразование после его преобразования в bool (компилятору разрешено делать 2 неявных преобразования за раз, но только 1 пользовательское преобразование при максимуме).

Поскольку компилятор не будет приводить "прошлое" bool, операторы явного преобразования теперь устраняют необходимость в идиоме Safe Bool . Например, умные указатели до C ++ 11 использовали идиому Safe Bool для предотвращения преобразований в целочисленные типы. В C ++ 11 интеллектуальные указатели вместо этого используют явный оператор, потому что компилятору не разрешается неявно преобразовывать в целочисленный тип после того, как он явно преобразовал тип в bool.

Продолжить до Перегрузка new и delete.

142 голосов
/ 12 декабря 2010

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

Примечание: Это относится только к синтаксису с перегрузкой new и delete, но не с реализация таких перегруженных операторов. Я думаю, что семантика перегрузки new и delete заслуживает своего собственного FAQ , в рамках темы перегрузки операторов я никогда не смогу отдать должное.

Основы

В C ++, когда вы пишете новое выражение , как new T(arg), при вычислении этого выражения происходят две вещи: Первая operator new вызывается для получения необработанной памяти, а затем вызывается соответствующий конструктор T, чтобы превратить эту необработанную память в действительный объект. Аналогично, когда вы удаляете объект, сначала вызывается его деструктор, а затем память возвращается к operator delete.
C ++ позволяет настроить обе эти операции: управление памятью и создание / уничтожение объекта в выделенной памяти. Последнее делается путем написания конструкторов и деструкторов для класса. Точная настройка управления памятью осуществляется написанием ваших собственных operator new и operator delete.

Первое из основных правил перегрузки операторов - не делайте этого - особенно относится к перегрузкам new и delete. Почти единственными причинами перегрузки этих операторов являются проблемы с производительностью и ограничения памяти , а во многих случаях другие действия, такие как Изменения в используемых алгоритмах обеспечат намного более высокое соотношение цена / выигрыш , чем попытка настроить управление памятью.

Стандартная библиотека C ++ поставляется с набором предопределенных операторов new и delete. Самые важные из них:

void* operator new(std::size_t) throw(std::bad_alloc); 
void  operator delete(void*) throw(); 
void* operator new[](std::size_t) throw(std::bad_alloc); 
void  operator delete[](void*) throw(); 

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

Размещение new

C ++ позволяет новым и удаляемым операторам принимать дополнительные аргументы.
Так называемое размещение нового позволяет вам создать объект по определенному адресу, который передается:

class X { /* ... */ };
char buffer[ sizeof(X) ];
void f()
{ 
  X* p = new(buffer) X(/*...*/);
  // ... 
  p->~X(); // call destructor 
} 

Стандартная библиотека поставляется с соответствующими перегрузками операторов new и delete для этого:

void* operator new(std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete(void* p,void*) throw(); 
void* operator new[](std::size_t,void* p) throw(std::bad_alloc); 
void  operator delete[](void* p,void*) throw(); 

Обратите внимание, что в приведенном выше примере кода для размещения новых, operator delete никогда не вызывается, если только конструктор X не сгенерирует исключение.

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

Новое в классе и удаление

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

class my_class { 
  public: 
    // ... 
    void* operator new();
    void  operator delete(void*,std::size_t);
    void* operator new[](size_t);
    void  operator delete[](void*,std::size_t);
    // ... 
}; 

Перегруженные таким образом, new и delete ведут себя как статические функции-члены. Для объектов my_class аргумент std::size_t всегда будет sizeof(my_class). Однако эти операторы также вызываются для динамически размещенных объектов производных классов , и в этом случае оно может быть больше этого значения.

Глобальный новый и удалить

Чтобы перегрузить глобальное новое и удалить, просто замените предопределенные операторы стандартной библиотеки нашими собственными. Однако это редко когда-либо нужно делать.

...