В чем причина сноски 37 в 3.7.4.2 [basic.stc.dynamic.deallocation]/2 в С++ 14?

§3.7.4.2/2 содержит следующие предложения:

Глобальная operator delete ровно с двумя параметрами, второй из которых имеет тип std::size_t, является обычной функцией освобождения памяти. Точно так же глобальная operator delete[] с ровно одним параметром является обычной функцией освобождения памяти. Глобальный оператор delete[] ровно с двумя параметрами, второй из которых имеет тип std::size_t, является обычной функцией освобождения памяти.37

37) Эта функция освобождения исключает использование функции распределения void operator new(std::size_t, std::size_t) в качестве функции распределения размещения.

Я не только не понял причины этой сноски, но также заметил, что этой формы размещения, упомянутой в сноске, не существует в §18.6.1.3 Формы размещения [new.delete.placement].

Изменить, чтобы проверить, что говорит @Sander De Dicker в своем ответе. , я протестировал следующий фрагмент:

#include <iostream>

void* operator new  (std::size_t count, int i1, int i2, int i3){
    void *p = malloc(count);
    if (!p) throw std::bad_alloc{};
    std::cout << "operator new" << '\n';
    return p;
}

void operator delete (void* p, int j1, int j2, int j3)
{
    free(p);
    std::cout << "operator delete" << '\n';
}

class A {
public:
    A() { std::cout << "A()" << '\n'; };
    ~A() { std::cout << "~A()" << '\n'; }
};

int main()
{
    try
    {
        A *p = new(0, 0, 0) A;
        delete p;
    }
    catch (std::bad_alloc&) { exit(1); }
}

Во всех трех доступных мне компиляторах (VS2015, clang и g++) код вызывал размещение operator new(size_t, int, int, int), но не вызывал размещение operator delete(void*, int, int, int), а operator delete(void*). Теперь я еще больше смущен, чем когда задавал вопрос.

Живой пример


person Belloc    schedule 30.11.2015    source источник
comment
Я думаю, это означает, что один (пользователь) не может определить operator new(size_t, size_t), потому что для этого требуется соответствующий operator delete(size_t, size_t) для удалений, которые происходят из-за исключений, возникающих во время построения в распределении с помощью этой новой функции размещения.   -  person dyp    schedule 30.11.2015
comment
@dyp Но, согласно §18.6.1.3, язык не допускает эту форму размещения для operator new.   -  person Belloc    schedule 30.11.2015
comment
Я не совсем понимаю, как [new.delete.placement] запрещает эту функцию распределения. Не могли бы вы уточнить? Примечание. Я использовал термин «требует» в своем предыдущем комментарии, но это слишком сильно: соответствующая функция освобождения не требуется, но без соответствующей функции освобождения вы получите утечку в этот сценарий. (Я не уверен, будет ли вызываться библиотечная версия.)   -  person dyp    schedule 30.11.2015
comment
@dyp Насколько я понимаю, в §18.6.1.3 указано, какие формы размещения разрешены языком.   -  person Belloc    schedule 30.11.2015
comment
Удаление места размещения — странный зверь; он вызывается только тогда, когда конструктор, вызываемый размещением new, выбрасывает.   -  person T.C.    schedule 30.11.2015
comment
@Т.С. Я могу подтвердить это, бросив внутрь конструктора A(). В этом случае вызывается operator delete(void*, int, int, int). Но согласно §5.3.4/22 его также следует вызывать в приведенном выше примере. Почему это не так?   -  person Belloc    schedule 30.11.2015
comment
Этот абзац имеет дело только с обработкой освобождения памяти, когда конструкция бросает вызов. delete x; указывается отдельно (в 5.3.5 [expr.delete]) и использует только обычные функции освобождения. Практическая причина: компилятор знает только, какие аргументы передать размещению удаления, когда вы находитесь в размещении new.   -  person T.C.    schedule 30.11.2015
comment
@Т.С. Practical reason: the compiler only knows what arguments to pass to the placement delete while you are in the placement new. Великолепно.   -  person Belloc    schedule 30.11.2015
comment
@Т.С. Это странно: когда A() бросает, но исключение не перехватывается в main(), ни ~A(), ни operator delete(void*, int, int, int) не вызываются. Я пытался найти объяснение, но безрезультатно.   -  person Belloc    schedule 01.12.2015
comment
~A() никогда не будет вызываться, так как A никогда не создается успешно. Освобождение является частью раскручивания стека, и когда соответствующий обработчик не найден, от реализации зависит, раскручивается ли стек.   -  person T.C.    schedule 01.12.2015
comment
@Т.С. Я не должен был жаловаться на ~A(). Это было просто моим отвлечением. Но для того, чтобы operator delete(void*, int, int, int) был вызван, исключение, генерируемое A(), должно быть перехвачено блоком try, окружающим вызов конструктора. Разве это не приведет к раскручиванию стека?   -  person Belloc    schedule 01.12.2015
comment
@Belloc Вы предполагаете неявный try-catch(...)-deallocate-rethrow вокруг new. Мне нужно проверить, как это указано в стандарте, но реализации по существу рассматривают освобождение при броске как еще один деструктор, который запускается, когда происходит раскручивание стека. То есть это точно так же, как если бы вы вызывали operator new, скрывали результат в охраннике области, который вызывает функцию освобождения при уничтожении, вызываете конструктор, а затем отклоняете охрану области. Я могу написать это вручную и заставить clang/gcc создать идентичную сборку.   -  person T.C.    schedule 01.12.2015
comment
@Т.С. Это (текущее) обсуждение помогло мне понять, что вы сказали выше. Спасибо.   -  person Belloc    schedule 02.12.2015


Ответы (1)


C.3.2 [diff.cpp11.basic] поясняет, что:

C.3.2, пункт 3: основные понятия [diff.cpp11.basic]

3.7.4.2

Изменение: новый обычный (без размещения) делокатор

Обоснование: требуется для масштабного освобождения.

Влияние на исходную функцию. Действительный код C++ 2011 может объявить глобальную функцию распределения размещения и функцию освобождения следующим образом:

void operator new(std::size_t, std::size_t);

void operator delete(void*, std::size_t) noexcept;

Однако в этом международном стандарте объявление operator delete может соответствовать предварительно определенному обычному (без размещения) operator delete (3.7.4). Если это так, то программа плохо сформирована, как это было для функций выделения членов класса и функций освобождения (5.3.4).

Другими словами, это кардинальное изменение предыдущего стандарта (C++11). В C++11 было разрешено определять такие operator delete, тогда как в C++14 программа была бы плохо сформирована.

Правка – дополнительные разъяснения на основе комментариев:

  • 18.6.1.3 [new.delete.placement] перечисляет зарезервированные формы размещения. Однако вам разрешено объявлять другие (незарезервированные) в вашем коде. Итак, эта часть стандарта не относится к вашему вопросу.

  • 3.7.4.2 [basic.stc.dynamic.deallocation] содержит упомянутое выше изменение по сравнению с C++11, которое запрещает формы размещения operator new(std::size_t, std::size_t) и operator delete(void*, std::size_t), поскольку последнее может соответствовать предопределенному неразмещению. operator delete. Это изменение подробно описано в C.3.2 [diff.cpp11.basic].

  • Ваш отредактированный вопрос уже обсуждался в комментариях (1, 2) от @TC , но я включу его здесь для полноты картины: причина, по которой ваш пользовательский operator delete(void*, int, int, int) не вызывается, заключается в том, что operator new(std::size_t, int, int, int) не вызвало исключение. 5.3.4 [expr.new] объясняет это в §20–23.

person Sander De Dycker    schedule 30.11.2015
comment
См. также: [expr.new]p22 (в недавнем черновике; не уверен, что он уже есть в C++14) - person dyp; 30.11.2015
comment
С++ 11 также не упоминает формы размещения, которые вы указали выше в §18.6.1.3. Насколько я понимаю, в §18.6.1.3 указано, какие формы размещения разрешены языком. - person Belloc; 30.11.2015
comment
@Belloc: 18.6.1.3 перечисляет зарезервированные формы размещения. Это не означает, что вы не можете объявлять другие (т.е. незарезервированные) в своем коде. Однако в сноске, о которой вы спрашиваете, говорится, что, кроме того, вам также не разрешено объявлять функцию распределения размещения void operator new(std::size_t, std::size_t) (это было разрешено в С++ 11, но больше не в С++ 14). - person Sander De Dycker; 30.11.2015
comment
Для меня слово reserved означает то, что только что написано в §18.6.1.3/1, а именно: a C++ program may not define functions that displace the versions in the Standard C++ library. Другими словами, формы размещения не заменяемы. - person Belloc; 30.11.2015
comment
@Belloc: для смещения сигнатура функции должна соответствовать перемещаемой версии. Поскольку void operator new(std::size_t, std::size_t) не зарезервировано в соответствии с 18.6.1.3, этот параграф не применяется. Однако сноска, которую вы упомянули, делает. - person Sander De Dycker; 30.11.2015
comment
Может быть, вы правы. Но где в стандарте я могу найти ссылку на тот факт, что пользователь может свободно определять эти так называемые незарезервированные формы размещения? - person Belloc; 30.11.2015
comment
@Belloc: я не знаю, прямо ли указано в стандарте, что это разрешено, но это не запрещено. На самом деле, если вы прочитаете, например, 5.3.4 [expr.new], вы найдете описание и примеры того, как это сделать. Кроме того, в C.3.2 (см. мой ответ) конкретно упоминается, что это разрешено в С++ 11. - person Sander De Dycker; 30.11.2015
comment
См. раздел Изменить в моем вопросе выше. - person Belloc; 30.11.2015