2458. N3778 and new library deallocation signatures

Section: 17.6 [support.dynamic], 17.6.3.2 [new.delete.single], 17.6.3.3 [new.delete.array] Status: C++17 Submitter: Richard Smith Opened: 2014-11-23 Last modified: 2017-07-30

Priority: 2

View all other issues in [support.dynamic].

View all issues with C++17 status.

Discussion:

N3778 added the following sized deallocation signatures to the library:

void operator delete(void* ptr, std::size_t size) noexcept;
void operator delete[](void* ptr, std::size_t size) noexcept;

void operator delete(void* ptr, std::size_t size, const std::nothrow_t&) noexcept;
void operator delete[](void* ptr, std::size_t size, const std::nothrow_t&) noexcept;

The former two are an essential part of the proposal. The latter two seem spurious — they are not called when new (std::nothrow) X fails due to X::X() throwing, because the core language rules for selecting a placement deallocation function do not consider passing a size argument. Instead, the above would be the matching deallocation functions for:

void *operator new(std::size_t size, std::size_t size_again, const std::nothrow_t&) noexcept;
void *operator new[](std::size_t size, std::size_t size_again, const std::nothrow_t&) noexcept;

... which don't exist.

Since they're not implicitly called, the only other possible use for those functions would be to perform an explicitly non-throwing deallocation. But... the first two overloads are already explicitly non-throwing and are required to be semantically identical to the second two. So there's no point in making an explicit call to the second pair of functions either.

It seems to me that we should remove the (void*, size_t, nothrow_t) overloads, because the core working group decided during the Urbana 2014 meeting, that no change to the core language was warranted.

[2014-11-23, Daniel suggests concrete wording changes]

[2015-02 Cologne]

Nobody can call those overloads, since the nothrow allocation functions cannot throw. JY: Ship it. GR: Should we do due diligence and make sure we're deleting what we mean to be deleting? [Some checking, everything looks good.]

Accepted.

Proposed resolution:

This wording is relative to N4140.

  1. Change 17.6 [support.dynamic], header <new> synopsis, as indicated:

    […]
    void operator delete(void* ptr, std::size_t size) noexcept;
    void operator delete(void* ptr, std::size_t size, const std::nothrow_t&) noexcept;
    […]
    void operator delete[](void* ptr, std::size_t size) noexcept;
    void operator delete[](void* ptr, std::size_t size, const std::nothrow_t&) noexcept;
    […]
    
  2. Change 17.6.3.2 [new.delete.single], starting before p19, as indicated:

    void operator delete(void* ptr, const std::nothrow_t&) noexcept;
    void operator delete(void* ptr, std::size_t size, const std::nothrow_t&) noexcept;
    

    […]

    -20- Replaceable: a C++ program may define a function with signature void operator delete(void* ptr, const std::nothrow_t&) noexcept that displaces the default version defined by the C++ standard library. If this function (without size parameter) is defined, the program should also define void operator delete(void* ptr, std::size_t size, const std::nothrow_t&) noexcept. If this function with size parameter is defined, the program shall also define the version without the size parameter. [Note: The default behavior below may change in the future, which will require replacing both deallocation functions when replacing the allocation function. — end note]

    […]

    -22- Requires: If present, the std::size_t size argument must equal the size argument passed to the allocation function that returned ptr.

    -23- Required behavior: Calls to operator delete(void* ptr, std::size_t size, const std::nothrow_t&) may be changed to calls to operator delete(void* ptr, const std::nothrow_t&) without affecting memory allocation. [Note: A conforming implementation is for operator delete(void* ptr, std::size_t size, const std::nothrow_t&) to simply call operator delete(void* ptr, const std::nothrow_t&). — end note]

    -24- Default behavior: operator delete(void* ptr, std::size_t size, const std::nothrow_t&) calls operator delete(ptr, std::nothrow), and operator delete(void* ptr, const std::nothrow_t&) calls operator delete(ptr).

  3. Change 17.6.3.3 [new.delete.array], starting before p16, as indicated:

    void operator delete[](void* ptr, const std::nothrow_t&) noexcept;
    void operator delete[](void* ptr, std::size_t size, const std::nothrow_t&) noexcept;
    

    […]

    -17- Replaceable: a C++ program may define a function with signature void operator delete[](void* ptr, const std::nothrow_t&) noexcept that displaces the default version defined by the C++ standard library. If this function (without size parameter) is defined, the program should also define void operator delete[](void* ptr, std::size_t size, const std::nothrow_t&) noexcept. If this function with size parameter is defined, the program shall also define the version without the size parameter. [Note: The default behavior below may change in the future, which will require replacing both deallocation functions when replacing the allocation function. — end note]

    […]

    -19- Requires: If present, the std::size_t size argument must equal the size argument passed to the allocation function that returned ptr.

    -20- Required behavior: Calls to operator delete[](void* ptr, std::size_t size, const std::nothrow_t&) may be changed to calls to operator delete[](void* ptr, const std::nothrow_t&) without affecting memory allocation. [Note: A conforming implementation is for operator delete[](void* ptr, std::size_t size, const std::nothrow_t&) to simply call operator delete[](void* ptr, const std::nothrow_t&). — end note]

    -21- Default behavior: operator delete[](void* ptr, std::size_t size, const std::nothrow_t&) calls operator delete[](ptr, std::nothrow), and operator delete[](void* ptr, const std::nothrow_t&) calls operator delete[](ptr).