1

I am trying to compile C++14 project of mine with gcc 6.3 and I am getting:

hm3/pairblob.cc: In static member function 'static std::unique_ptr<hm3::PairBlob> hm3::PairBlob::create(const hm3::PairBlob*)':
hm3/pairblob.cc:64:44: error: exception cleanup for this placement new selects non-placement operator delete [-fpermissive]
  std::unique_ptr<PairBlob> pair{ new(size) PairBlob };
                                            ^~~~~~~~
In file included from /usr/include/c++/6.3.1/ext/new_allocator.h:33:0,
                 from /usr/include/c++/6.3.1/armv7l-unknown-linux-gnueabihf/bits/c++allocator.h:33,
                 from /usr/include/c++/6.3.1/bits/allocator.h:46,
                 from /usr/include/c++/6.3.1/memory:63,
                 from hm3/pairblob.h:10,
                 from hm3/pairblob.cc:1:
/usr/include/c++/6.3.1/new:125:6: note: 'void operator delete(void*, std::size_t)' is a usual (non-placement) deallocation function in C++14 (or with -fsized-deallocation)
 void operator delete(void*, std::size_t) _GLIBCXX_USE_NOEXCEPT
      ^~~~~~~~

Code is relatively complicated but this is the part in question:

std::unique_ptr<PairBlob> PairBlob::create(const PairBlob *src){
    if (src == nullptr)
        return {};

    size_t const size = src->getBytes();

    std::unique_ptr<PairBlob> pair{ new(size) PairBlob };

    memcpy(pair.get(), src, size);

    return pair;
}

This PairBlob::create is a static method. PairBlob is a POD and have private default c-tor and have no d-tor:

struct PairBlob{
private:
    PairBlob() = default;

    static void *operator new(size_t, size_t const size){
        return ::operator new(size);
    }

    static void *operator new(size_t, size_t const size, const std::nothrow_t){
        return ::operator new(size, std::nothrow);
    }

// ...

Error appear only on C++14, compiling with C++11 is without error. I am currently working on 32 bit ARM but I do not think this is related.

UPDATE

I found if I comment out the c-tor, code works. But in this case the c-tor is public and this is not what I want.

If I do code like this, it does give same error

PairBlob() noexcept = default;

UPDATE

I because I am providing new operators, I decided to provide delete as well. Now it compiles correctly. I did put some noexcept but they are not really required.

struct PairBlob{
private:
    PairBlob() = default;

    static void *operator new(size_t, size_t const size){
        return ::operator new(size);
    }

    static void *operator new(size_t, size_t const size, const std::nothrow_t) noexcept{
        return ::operator new(size, std::nothrow);
    }

public:
    // fixing C++14 error
    static void operator delete(void* memory){
        ::operator delete(memory);
    }

I am not posting this as answer, because I still do not understand why gcc can not use "normal" operator delete.

Nick
  • 9,962
  • 4
  • 42
  • 80
  • According to the error, your `unique_ptr` initialisation appears to be mapping to a placement-`new` somehow. That presumably is not what you actually want. – underscore_d May 24 '17 at 14:59
  • it is placement new - `new(size) PairBlob` . this is exactly what I want. – Nick May 24 '17 at 15:09
  • I can allocate space myself, check for errors, then memcpy over it, and finally give the result to unique_ptr, but if I do it this way it will be not C++ way. Also I want this to throw an exception if there is no memory – Nick May 24 '17 at 15:12
  • 1
    I must not know enough about placement `new`, as I don't see how passing it a `size_t` can do anything good. Anyway: `/usr/include/c++/6.3.1/new:125:6: note: 'void operator delete(void*, std::size_t)' is a usual (non-placement) deallocation function in C++14 (or with -fsized-deallocation)` Have you tried manually implementing a 'placement deallocation function' using a custom deleter? As per https://stackoverflow.com/questions/6730403/how-to-delete-object-constructed-via-placement-new-operator. Edit: This one seems better: https://stackoverflow.com/a/17656780/2757035 – underscore_d May 24 '17 at 15:15
  • Possible duplicate of [What does the error "non-placement deallocation function"?](https://stackoverflow.com/questions/5367674/what-does-the-error-non-placement-deallocation-function) or perhaps better, [Placement deallocation function is not called](https://stackoverflow.com/questions/25019041/placement-deallocation-function-is-not-called) – underscore_d May 24 '17 at 15:23
  • @underscore_d - saw it prior posting, it is similar, but not the same. – Nick May 24 '17 at 15:37

1 Answers1

2

Short answer:

As of C++14, global function void operator delete(void*, size_t) noexcept is defined as a usual (non-placement) delete function, and thus is no longer considered a placement delete function (this behaviour matches static member function void T::operator delete(void*, size_t) noexcept, which is a usual delete function). This means it can't match any placement new functions, causing the error when lookup discovers it while looking for void* PairBlob::operator new(size_t, size_t)'s match.

Adding void PairBlob::operator delete(void*) prevents lookup from discovering the aforementioned global operator delete() overload, and thus the placement new won't call a placement delete if the constructor throws.


Long answer:

Compare the functions described in [new.delete.single] in C++11 and C++14 (all information other than delete function signatures omitted for brevity).

C++11

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

C++14

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

Examination of C++14 [new.delete.single/14] appears to indicate that if present, size must be the value implicitly passed to the usual void* operator new(size_t), when called as new Whatever.

As specified in [basic.stc.dynamic/2], out of these single-object delete functions, the following are implicitly declared, in global scope, in all translation units:

C++11

void operator delete(void*);

C++14

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

Similarly, in C++14, [basic.stc.dynamic.deallocation/2] contains this clause:

The global operator delete with exactly two parameters, the second of which has type std::size_t, is a usual deallocation function.

This clause isn't present in the C++11 version of [basic.stc.dynamic.deallocation/2].

Furthermore, footnote 37 in the C++14 version, referring to operator delete(void* ptr, size_t) noexcept and operator delete[](void* ptr, size_t) noexcept, states that:

This deallocation function precludes use of an allocation function void operator new(std::size_t, std::size_t) as a placement allocation function ([diff.cpp11.basic]).

The linked page notes that this change may break valid C++11 code which relies on global function void operator delete(void*, size_t) noexcept being a placement delete function, causing the program to be ill-formed when compiled as C++14. This matches the sized delete function's behaviour as a class member, where code that relies on static member function void T::operator delete(void*, size_t) noexcept being a placement delete function is ill-formed.

In regards to static member function void T::operator delete(void*, size_t), [basic.stc.dynamic.deallocation/2] states, in both C++11 and C++14, that:

If a class T has a member deallocation function named operator delete with exactly one parameter, then that function is a usual deallocation function. If class T does not declare such an operator delete but does declare a member deallocation function named operator delete with exactly two parameters, the second of which has type std::size_t, then this function is a usual deallocation function.

Quote taken from C++14 version. The C++11 version clarifies that "usual" means "non-placement", and links to [support.types] for a definition of std::size_t. The C++14 version clarifies the meaning of "usual" before reaching this clause, and omits the link to [support.types].


[expr.new] (specifically, [expr.new/20] in C++11, and [expr.new/22] in C++14) indicates that for a call to any placement new function, it will try to find a corresponding placement delete function via lookup, which will be called if the constructor throws when using that placement new; an operator delete() will be considered a match if its parameter list matches the operator new()'s parameter list (with the exception of the first parameter, which must always be size_t for operator new() and void* for operator delete()). As deallocation functions must be either global or class members, name lookup rules state that when matching T::operator new(), it will fall back to the global operator delete()s if no member operator delete()s are found. [See here for a demonstration of this behaviour.]

If a corresponding function is discovered by lookup, and that function is a placement delete function, all is well and good; if it's not, however, then the program is ill-formed. Therein lies the rub; since PairBlob doesn't originally contain an operator delete(), lookup falls back to the global scope, and locates void operator delete(void*, size_t) (if present). This is a placement delete function in C++11 or earlier (and may not even be present, in which case see the below paragraph), but an implicitly declared usual delete function in C++14 or later (and thus always present), meaning that any code which relies on it as a placement delete becomes ill-formed during the transition from C++11 to C++14.

If no corresponding function is discovered by lookup, however, then no placement delete function will be called if the constructor throws. This is why adding void PairBlob::operator delete(void*) removes the error; as lookup stops when it finds this function, the global operator delete(void*, size_t) noexcept will not be discovered, and thus the program won't become ill-formed by trying to use it as a placement delete function. [Note, however, that adding void PairBlob::operator delete(void*, size_t) would cause the same error as relying on the global one, as static void T::operator delete(void*, size_t) noexcept is also a usual delete function.]

GCC 6 defaults to C++14, which has caused this to occur before. MSVC 2015 and later will also catch this, and emit error C2956; it doesn't appear to have a corresponding MSDN page at the moment, but this bug report explicitly refers to [expr.new/22] (albeit by its number, § 5.3.4/22, rather than its name). Surprisingly, however, Clang fails to catch it, and will continue to treat void operator delete(void*, size_t) noexcept as a placement delete. [Compare them here, if you want.]

As mentioned on the GCC link above, the best solution is to modify the code to be compatible with C++14, but compiling with a previous C++ standard is a viable workaround. Adding a member operator delete() that prevents name lookup from finding the global one does indeed prevent this problem, but I'm not sure whether it would be better to modify the placement new instead.