问题
Pedantically, this may not be OK. As per cppref:
If expression is anything else, including if it is a pointer obtained by the array form of new-expression, the behavior is undefined.
Putting that aside, is the following code OK in practice (T
is non-array, and assuming that new
is not replaced)?
auto p = (T*)operator new(sizeof(T));
new(p) T{};
delete p;
It is said that in cppref that
When calling the allocation function, the new-expression passes the number of bytes requested as the first argument, of type
std::size_t
, which is exactlysizeof(T)
for non-array T.
So I guess this is probably OK. However, it is also said that since C++14,
New-expressions are allowed to elide or combine allocations made through replaceable allocation functions. In case of elision, the storage may be provided by the compiler without making the call to an allocation function (this also permits optimizing out unused new-expression). In case of combining, the allocation made by a new-expression E1 may be extended to provide additional storage for another new-expression E2 if all of the following is true: [...]
Note that this optimization is only permitted when new-expressions are used, not any other methods to call a replaceable allocation function:
delete[] new int[10];
can be optimized out, butoperator delete(operator new(10));
cannot.
I'm not quite sure of the implications. So, is this OK in C++14?
Why am I asking this question? (source)
Sometimes, memory allocation and initialization cannot be done in a single step. You have to manually allocate the memory, do something else, and then initialize the object, e.g., to provide strong exception safety. In this case, if the delete expression cannot be used on the resulting pointer, you have to manually uninitialize and deallocate, which is tedious. To make things worse, in case both new expression and the manual method are employed, you have to track which one is used for each object.
回答1:
This code has well-defined behavior if you have p = new(p) T{};
, assuming that no custom (de)allocation functions are in play. (It can be easily hardened against such things; doing so is left as an exercise for the reader.)
The rule is that you must give non-array delete
a pointer to an object created by a non-array new
(or a base class (with virtual destructor) subobject of such an object, or a null pointer). And with that change your code does that.
There's no requirement that the non-array new
be of a non-placement form. There can't be, since you'd better be able to delete new(std::nothrow) int
. And that's a placement new-expression too, even though people don't usually mean that when they talk about "placement new".
delete
is defined to result in a call to a deallocation function (ignoring the elision cases, which is irrelevant here because the only allocation function called by the new-expression is not a replaceable global allocation function). If you set things up so that it passes invalid arguments to that deallocation function, then you get undefined behavior from that. But here it passes the right address (and the right size if a sized deallocation function is used), so it's well-defined.
回答2:
I didn't quite grasp OP's last paragraph. What is tedious about explicitly calling the destructor and then deallocating the pointer? That is what STL and any reputable lib that I'v seen do all the time. And yes, you have to keep track of how you allocate memory, in order to deallocate it back. Reading the api docs of std::vector and/or std::shared_ptr can familiarize one with the correct way of doing things. If you are unfamiliar with the syntax of explicit dtor call here is a simple snippet:
void * raw_ptr= my_alloc(sizeof(my_type),alignof(my_type));
if (!raw_ptr)
throw my_bad_alloc();
my_type* ptr {new(raw_ptr) my_type{init_params}};
raw_ptr=nullptr;
//...
ptr->~my_type();//hello! Is it me you're lookin' for?
if (!my_dealloc(ptr))
throw my_runtime_error();
ptr=nullptr;
来源:https://stackoverflow.com/questions/49546754/is-it-practically-ok-to-delete-object-not-constructed-using-the-new-expression