Is it OK to make a placement new on memory managed by a smart pointer?












12














Context



For test purpose, I need to construct an object on non-zero memory. This could be done with:



{
struct Type { /* IRL not empty */};
std::array<unsigned char, sizeof(Type)> non_zero_memory;
non_zero_memory.fill(0xC5);
auto const& t = *new(non_zero_memory.data()) Type;
// t refers to a valid Type whose initialization has completed.
t.~Type();
}


Since this is tedious and made multiple times, I'd like to provide a function returning a smart pointer to such a Type instance. I came up with the following, but I fear undefined behavior lurk somewhere.



Question



Is the following program well defined? Especially, is the fact that a std::byte has been allocated but a Type of equivalent size is freed an issue?



#include <cstddef>
#include <memory>
#include <algorithm>

auto non_zero_memory(std::size_t size)
{
constexpr std::byte non_zero = static_cast<std::byte>(0xC5);

auto memory = std::make_unique<std::byte>(size);
std::fill(memory.get(), memory.get()+size, non_zero);
return memory;
}

template <class T>
auto on_non_zero_memory()
{
auto memory = non_zero_memory(sizeof(T));
return std::shared_ptr<T>(new (memory.release()) T());
}

int main()
{
struct Type { unsigned value = 0; ~Type() {} }; // could be something else
auto t = on_non_zero_memory<Type>();
return t->value;
}


Live demo










share|improve this question




















  • 1




    There are rules about objects replacing other objects, and rules about trivial objects not needing explicit destruction - I'll let someone find the proper wording to prove you're safe though
    – Lightness Races in Orbit
    5 hours ago










  • The memory isn't really managed by a smart pointer since you released it.
    – François Andrieux
    5 hours ago










  • @FrançoisAndrieux only to let it be managed by another smart pointer though.
    – YSC
    5 hours ago










  • @LightnessRacesinOrbit Type is not necessarily that simple. Let me correct that... Q fixed.
    – YSC
    5 hours ago








  • 1




    The first code snippet is generally incorrect, since you do not care proper alignment for objects of type Type. Better to use std::aligned_storage_t<sizeof(Type), alignof(Type)> than std::array of chars.
    – Daniel Langr
    4 hours ago


















12














Context



For test purpose, I need to construct an object on non-zero memory. This could be done with:



{
struct Type { /* IRL not empty */};
std::array<unsigned char, sizeof(Type)> non_zero_memory;
non_zero_memory.fill(0xC5);
auto const& t = *new(non_zero_memory.data()) Type;
// t refers to a valid Type whose initialization has completed.
t.~Type();
}


Since this is tedious and made multiple times, I'd like to provide a function returning a smart pointer to such a Type instance. I came up with the following, but I fear undefined behavior lurk somewhere.



Question



Is the following program well defined? Especially, is the fact that a std::byte has been allocated but a Type of equivalent size is freed an issue?



#include <cstddef>
#include <memory>
#include <algorithm>

auto non_zero_memory(std::size_t size)
{
constexpr std::byte non_zero = static_cast<std::byte>(0xC5);

auto memory = std::make_unique<std::byte>(size);
std::fill(memory.get(), memory.get()+size, non_zero);
return memory;
}

template <class T>
auto on_non_zero_memory()
{
auto memory = non_zero_memory(sizeof(T));
return std::shared_ptr<T>(new (memory.release()) T());
}

int main()
{
struct Type { unsigned value = 0; ~Type() {} }; // could be something else
auto t = on_non_zero_memory<Type>();
return t->value;
}


Live demo










share|improve this question




















  • 1




    There are rules about objects replacing other objects, and rules about trivial objects not needing explicit destruction - I'll let someone find the proper wording to prove you're safe though
    – Lightness Races in Orbit
    5 hours ago










  • The memory isn't really managed by a smart pointer since you released it.
    – François Andrieux
    5 hours ago










  • @FrançoisAndrieux only to let it be managed by another smart pointer though.
    – YSC
    5 hours ago










  • @LightnessRacesinOrbit Type is not necessarily that simple. Let me correct that... Q fixed.
    – YSC
    5 hours ago








  • 1




    The first code snippet is generally incorrect, since you do not care proper alignment for objects of type Type. Better to use std::aligned_storage_t<sizeof(Type), alignof(Type)> than std::array of chars.
    – Daniel Langr
    4 hours ago
















12












12








12


4





Context



For test purpose, I need to construct an object on non-zero memory. This could be done with:



{
struct Type { /* IRL not empty */};
std::array<unsigned char, sizeof(Type)> non_zero_memory;
non_zero_memory.fill(0xC5);
auto const& t = *new(non_zero_memory.data()) Type;
// t refers to a valid Type whose initialization has completed.
t.~Type();
}


Since this is tedious and made multiple times, I'd like to provide a function returning a smart pointer to such a Type instance. I came up with the following, but I fear undefined behavior lurk somewhere.



Question



Is the following program well defined? Especially, is the fact that a std::byte has been allocated but a Type of equivalent size is freed an issue?



#include <cstddef>
#include <memory>
#include <algorithm>

auto non_zero_memory(std::size_t size)
{
constexpr std::byte non_zero = static_cast<std::byte>(0xC5);

auto memory = std::make_unique<std::byte>(size);
std::fill(memory.get(), memory.get()+size, non_zero);
return memory;
}

template <class T>
auto on_non_zero_memory()
{
auto memory = non_zero_memory(sizeof(T));
return std::shared_ptr<T>(new (memory.release()) T());
}

int main()
{
struct Type { unsigned value = 0; ~Type() {} }; // could be something else
auto t = on_non_zero_memory<Type>();
return t->value;
}


Live demo










share|improve this question















Context



For test purpose, I need to construct an object on non-zero memory. This could be done with:



{
struct Type { /* IRL not empty */};
std::array<unsigned char, sizeof(Type)> non_zero_memory;
non_zero_memory.fill(0xC5);
auto const& t = *new(non_zero_memory.data()) Type;
// t refers to a valid Type whose initialization has completed.
t.~Type();
}


Since this is tedious and made multiple times, I'd like to provide a function returning a smart pointer to such a Type instance. I came up with the following, but I fear undefined behavior lurk somewhere.



Question



Is the following program well defined? Especially, is the fact that a std::byte has been allocated but a Type of equivalent size is freed an issue?



#include <cstddef>
#include <memory>
#include <algorithm>

auto non_zero_memory(std::size_t size)
{
constexpr std::byte non_zero = static_cast<std::byte>(0xC5);

auto memory = std::make_unique<std::byte>(size);
std::fill(memory.get(), memory.get()+size, non_zero);
return memory;
}

template <class T>
auto on_non_zero_memory()
{
auto memory = non_zero_memory(sizeof(T));
return std::shared_ptr<T>(new (memory.release()) T());
}

int main()
{
struct Type { unsigned value = 0; ~Type() {} }; // could be something else
auto t = on_non_zero_memory<Type>();
return t->value;
}


Live demo







c++ language-lawyer c++17 smart-pointers undefined-behavior






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 5 hours ago







YSC

















asked 5 hours ago









YSCYSC

20.9k34595




20.9k34595








  • 1




    There are rules about objects replacing other objects, and rules about trivial objects not needing explicit destruction - I'll let someone find the proper wording to prove you're safe though
    – Lightness Races in Orbit
    5 hours ago










  • The memory isn't really managed by a smart pointer since you released it.
    – François Andrieux
    5 hours ago










  • @FrançoisAndrieux only to let it be managed by another smart pointer though.
    – YSC
    5 hours ago










  • @LightnessRacesinOrbit Type is not necessarily that simple. Let me correct that... Q fixed.
    – YSC
    5 hours ago








  • 1




    The first code snippet is generally incorrect, since you do not care proper alignment for objects of type Type. Better to use std::aligned_storage_t<sizeof(Type), alignof(Type)> than std::array of chars.
    – Daniel Langr
    4 hours ago
















  • 1




    There are rules about objects replacing other objects, and rules about trivial objects not needing explicit destruction - I'll let someone find the proper wording to prove you're safe though
    – Lightness Races in Orbit
    5 hours ago










  • The memory isn't really managed by a smart pointer since you released it.
    – François Andrieux
    5 hours ago










  • @FrançoisAndrieux only to let it be managed by another smart pointer though.
    – YSC
    5 hours ago










  • @LightnessRacesinOrbit Type is not necessarily that simple. Let me correct that... Q fixed.
    – YSC
    5 hours ago








  • 1




    The first code snippet is generally incorrect, since you do not care proper alignment for objects of type Type. Better to use std::aligned_storage_t<sizeof(Type), alignof(Type)> than std::array of chars.
    – Daniel Langr
    4 hours ago










1




1




There are rules about objects replacing other objects, and rules about trivial objects not needing explicit destruction - I'll let someone find the proper wording to prove you're safe though
– Lightness Races in Orbit
5 hours ago




There are rules about objects replacing other objects, and rules about trivial objects not needing explicit destruction - I'll let someone find the proper wording to prove you're safe though
– Lightness Races in Orbit
5 hours ago












The memory isn't really managed by a smart pointer since you released it.
– François Andrieux
5 hours ago




The memory isn't really managed by a smart pointer since you released it.
– François Andrieux
5 hours ago












@FrançoisAndrieux only to let it be managed by another smart pointer though.
– YSC
5 hours ago




@FrançoisAndrieux only to let it be managed by another smart pointer though.
– YSC
5 hours ago












@LightnessRacesinOrbit Type is not necessarily that simple. Let me correct that... Q fixed.
– YSC
5 hours ago






@LightnessRacesinOrbit Type is not necessarily that simple. Let me correct that... Q fixed.
– YSC
5 hours ago






1




1




The first code snippet is generally incorrect, since you do not care proper alignment for objects of type Type. Better to use std::aligned_storage_t<sizeof(Type), alignof(Type)> than std::array of chars.
– Daniel Langr
4 hours ago






The first code snippet is generally incorrect, since you do not care proper alignment for objects of type Type. Better to use std::aligned_storage_t<sizeof(Type), alignof(Type)> than std::array of chars.
– Daniel Langr
4 hours ago














2 Answers
2






active

oldest

votes


















13














This program is not well defined.



The rule is that if a type has a trivial destructor (See this), you don't need to call it. So, this:



return std::shared_ptr<T>(new (memory.release()) T());


is almost correct. It omits the destructor of the sizeof(T) std::bytes, which is fine, constructs a new T in the memory, which is fine, and then when the shared_ptr is ready to delete, it calls delete this->get();, which is wrong. That first deconstructs a T, but then it deallocates a T instead of a std::byte, which will probably (undefined) not work.



C++ standard §8.5.2.4p8 [expr.new]




A new-expression may obtain storage for the object by calling an allocation function. [...] If the allocated type is an array type, the allocation function's name is operator new.




(All those "may"s are because implementations are allowed to merge adjacent new expressions and only call operator new for one of them, but this isn't the case as new only happens once (In make_unique))



And part 11 of the same section:




When a new-expression calls an allocation function and that allocation has not been extended, the new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array. For arrays of char, unsigned char, and std::byte, the difference between the result of the new-expression and the address returned by the
allocation function shall be an integral multiple of the strictest fundamental alignment requirement (6.6.5) of any object type whose size is no greater than the size of the array being created. [Note: Because allocation
functions are assumed to return pointers to storage that is appropriately aligned for objects of any type with fundamental alignment, this constraint on array allocation overhead permits the common idiom of allocating
character arrays into which objects of other types will later be placed. — end note ]




If you read §21.6.2 [new.delete.array], you see that the default operator new and operator delete do the exact same things as operator new and operator delete, the problem is we don't know the size passed to it, and it is probably more than what delete ((T*) object) calls (to store the size).



Looking at what delete-expressions do:



§8.5.2.5p8 [expr.delete]




[...] delete-expression will invoke the destructor (if any) for [...] the elements of the array being deleted




p7.1




If the allocation call for the new-expression for the object to be deleted was not omitted [...], the delete-expression shall call a deallocation function (6.6.4.4.2). The value returned from the allocation call of the new-expression shall be passed as the first argument to the deallocation function.




Since std::byte does not have a destructor, we can safely call delete, as it will not do anything other than call the deallocate function (operator delete). We just have to reinterpret it back to std::byte*, and we will get back what new returned.



Another problem is that there is a memory leak if the constructor of T throws. A simple fix is to placement new while the memory is still owned by the std::unique_ptr, so even if it does throw it will call delete properly.



T* ptr = new (memory.get()) T();
memory.release();
return std::shared_ptr<T>(ptr, (T* ptr) {
ptr->~T();
delete reinterpret_cast<std::byte*>(ptr);
});


The first placement new ends the lifetime of the sizeof(T) std::bytes and starts the lifetime of a new T object at the same address, as according to §6.6.3p5 [basic.life]




A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. [...]




Then when it is being deleted, the lifetime of T ends by an explicit call of the destructor, and then according to the above, the delete-expression deallocates the storage.





This leads to the question of:



What if the storage class wasn't std::byte, and was not trivially destructible? Like, for example, we were using a non-trivial union as the storage.



Calling delete reinterpret_cast<T*>(ptr) would call the destructor on something that is not an object. This is clearly undefined behaviour, and is according to §6.6.3p6 [basic.life]




Before the lifetime of an object has started but after the storage which the object will occupy has been allocated [...], any pointer that represents the address of the storage location where the object will be or
was located may be used but only in limited ways. [...] The program has undefined behavior if: the object will be or was of a class type with a non-trivial destructor and the pointer is used as the operand of a delete-expression




So to use it like above, we have to construct it just to destruct it again.



The default constructor probably works fine. The usual semantics are "create an object that can be destructed", which is exactly what we want. Use std::uninitialized_default_construct_n to construct them all to then immediately destruct them:



    // Assuming we called `new StorageClass[n]` to allocate
ptr->~T();
auto* as_storage = reinterpret_cast<StorageClass*>(ptr);
std::uninitialized_default_construct_n(as_storage, n);
delete as_storage;


We can also call operator new and operator delete ourselves:



static void byte_deleter(std::byte* ptr) {
return ::operator delete(reinterpret_cast<void*>(ptr));
}

auto non_zero_memory(std::size_t size)
{
constexpr std::byte non_zero = static_cast<std::byte>(0xC5);

auto memory = std::unique_ptr<std::byte, void(*)(std::byte*)>(
reinterpret_cast<std::byte*>(::operator new(size)),
&::byte_deleter
);
std::fill(memory.get(), memory.get()+size, non_zero);
return memory;
}

template <class T>
auto on_non_zero_memory()
{
auto memory = non_zero_memory(sizeof(T));
T* ptr = new (memory.get()) T();
memory.release();
return std::shared_ptr<T>(ptr, (T* ptr) {
ptr->~T();
::operator delete(ptr, sizeof(T));
// ^~~~~~~~~ optional
});
}


But this looks a lot like std::malloc and std::free.



A third solution might be to use std::aligned_storage as the type given to new, and have the deleter work as with std::byte because the aligned storage is a trivial aggregate.






share|improve this answer























  • Alignment is only needed for over-aligned types though.
    – eerorika
    5 hours ago






  • 2




    @eerorika I thought it was UB to access (e.g) an int with alignment of 4 if it was not on a 4-byte boundary?
    – Artyer
    5 hours ago






  • 3




    Since this is a language lawyer question, it would be nice to have some form of authoritative source backing up the claims made.
    – François Andrieux
    5 hours ago






  • 1




    @Artyer check virgesmith's comment on the question. I would suggest fixing the exception safety in your answer for a complete example. François's comment after it has the solution.
    – eerorika
    5 hours ago








  • 2




    There is no destructor for std::byte so no non static member is being called. std::byte is defined as enum class byte : unsigned char {} ; so it is just a char
    – NathanOliver
    4 hours ago



















8














std::shared_ptr<T>(new (memory.release()) T())


Is undefined behavior. The memory that was acquired by memory was for a std::byte but the shared_ptr's deleter is doing to call delete on a pointer to T. Since the pointer no longer has the same type you can't call delete on it per [expr.delete]/2




In a single-object delete expression, the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject representing a base class of such an object. If not, the behavior is undefined.




You would have to provide the shared_ptr with a custom deleter that destroys T and then casts the pointer back to its source type and call delete on that.





It should also be noted that new (memory.release()) T() itself will be undefined if memory allocated a type that has non trivial destruction. You would have to call the destructor on the pointer from memory.release() first before you reuse it's memory.






share|improve this answer



















  • 1




    @FrançoisAndrieux Seems the standard requires the type of the thing you delete to match the thing you got back from new, regardless. Which seems like a bit of a bastard actually if that thing has been replaced.
    – Lightness Races in Orbit
    5 hours ago








  • 1




    This is true new ... delete is a problem. I though of the other points but not to that one. Nice catch.
    – YSC
    5 hours ago






  • 2




    Would it be sufficient to have a deleter that does ptr->~T(); delete ptr;? Edit : Might have to cast the pointer to std::byte* first, or maybe void* since you don't want a destructor call anyway. Edit 2 : deleteing a void* might be UB if it compiles at all.
    – François Andrieux
    5 hours ago








  • 1




    @FrançoisAndrieux maybe ptr->~T(); delete reinterpret_cast<std::byte*>(ptr);?
    – YSC
    5 hours ago






  • 1




    @NathanOliver Seems reasonable, though I think the lifetime of the array is over once you placement new over it. I'm not sure whether or not that's a problem. If it was an array of a more complex type, you would have needed to call the destructors ahead of the placement new so it would definitively not be OK (delete would destroy them again). Not sure if trivial types have an exemption for this.
    – François Andrieux
    5 hours ago













Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54095233%2fis-it-ok-to-make-a-placement-new-on-memory-managed-by-a-smart-pointer%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes









13














This program is not well defined.



The rule is that if a type has a trivial destructor (See this), you don't need to call it. So, this:



return std::shared_ptr<T>(new (memory.release()) T());


is almost correct. It omits the destructor of the sizeof(T) std::bytes, which is fine, constructs a new T in the memory, which is fine, and then when the shared_ptr is ready to delete, it calls delete this->get();, which is wrong. That first deconstructs a T, but then it deallocates a T instead of a std::byte, which will probably (undefined) not work.



C++ standard §8.5.2.4p8 [expr.new]




A new-expression may obtain storage for the object by calling an allocation function. [...] If the allocated type is an array type, the allocation function's name is operator new.




(All those "may"s are because implementations are allowed to merge adjacent new expressions and only call operator new for one of them, but this isn't the case as new only happens once (In make_unique))



And part 11 of the same section:




When a new-expression calls an allocation function and that allocation has not been extended, the new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array. For arrays of char, unsigned char, and std::byte, the difference between the result of the new-expression and the address returned by the
allocation function shall be an integral multiple of the strictest fundamental alignment requirement (6.6.5) of any object type whose size is no greater than the size of the array being created. [Note: Because allocation
functions are assumed to return pointers to storage that is appropriately aligned for objects of any type with fundamental alignment, this constraint on array allocation overhead permits the common idiom of allocating
character arrays into which objects of other types will later be placed. — end note ]




If you read §21.6.2 [new.delete.array], you see that the default operator new and operator delete do the exact same things as operator new and operator delete, the problem is we don't know the size passed to it, and it is probably more than what delete ((T*) object) calls (to store the size).



Looking at what delete-expressions do:



§8.5.2.5p8 [expr.delete]




[...] delete-expression will invoke the destructor (if any) for [...] the elements of the array being deleted




p7.1




If the allocation call for the new-expression for the object to be deleted was not omitted [...], the delete-expression shall call a deallocation function (6.6.4.4.2). The value returned from the allocation call of the new-expression shall be passed as the first argument to the deallocation function.




Since std::byte does not have a destructor, we can safely call delete, as it will not do anything other than call the deallocate function (operator delete). We just have to reinterpret it back to std::byte*, and we will get back what new returned.



Another problem is that there is a memory leak if the constructor of T throws. A simple fix is to placement new while the memory is still owned by the std::unique_ptr, so even if it does throw it will call delete properly.



T* ptr = new (memory.get()) T();
memory.release();
return std::shared_ptr<T>(ptr, (T* ptr) {
ptr->~T();
delete reinterpret_cast<std::byte*>(ptr);
});


The first placement new ends the lifetime of the sizeof(T) std::bytes and starts the lifetime of a new T object at the same address, as according to §6.6.3p5 [basic.life]




A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. [...]




Then when it is being deleted, the lifetime of T ends by an explicit call of the destructor, and then according to the above, the delete-expression deallocates the storage.





This leads to the question of:



What if the storage class wasn't std::byte, and was not trivially destructible? Like, for example, we were using a non-trivial union as the storage.



Calling delete reinterpret_cast<T*>(ptr) would call the destructor on something that is not an object. This is clearly undefined behaviour, and is according to §6.6.3p6 [basic.life]




Before the lifetime of an object has started but after the storage which the object will occupy has been allocated [...], any pointer that represents the address of the storage location where the object will be or
was located may be used but only in limited ways. [...] The program has undefined behavior if: the object will be or was of a class type with a non-trivial destructor and the pointer is used as the operand of a delete-expression




So to use it like above, we have to construct it just to destruct it again.



The default constructor probably works fine. The usual semantics are "create an object that can be destructed", which is exactly what we want. Use std::uninitialized_default_construct_n to construct them all to then immediately destruct them:



    // Assuming we called `new StorageClass[n]` to allocate
ptr->~T();
auto* as_storage = reinterpret_cast<StorageClass*>(ptr);
std::uninitialized_default_construct_n(as_storage, n);
delete as_storage;


We can also call operator new and operator delete ourselves:



static void byte_deleter(std::byte* ptr) {
return ::operator delete(reinterpret_cast<void*>(ptr));
}

auto non_zero_memory(std::size_t size)
{
constexpr std::byte non_zero = static_cast<std::byte>(0xC5);

auto memory = std::unique_ptr<std::byte, void(*)(std::byte*)>(
reinterpret_cast<std::byte*>(::operator new(size)),
&::byte_deleter
);
std::fill(memory.get(), memory.get()+size, non_zero);
return memory;
}

template <class T>
auto on_non_zero_memory()
{
auto memory = non_zero_memory(sizeof(T));
T* ptr = new (memory.get()) T();
memory.release();
return std::shared_ptr<T>(ptr, (T* ptr) {
ptr->~T();
::operator delete(ptr, sizeof(T));
// ^~~~~~~~~ optional
});
}


But this looks a lot like std::malloc and std::free.



A third solution might be to use std::aligned_storage as the type given to new, and have the deleter work as with std::byte because the aligned storage is a trivial aggregate.






share|improve this answer























  • Alignment is only needed for over-aligned types though.
    – eerorika
    5 hours ago






  • 2




    @eerorika I thought it was UB to access (e.g) an int with alignment of 4 if it was not on a 4-byte boundary?
    – Artyer
    5 hours ago






  • 3




    Since this is a language lawyer question, it would be nice to have some form of authoritative source backing up the claims made.
    – François Andrieux
    5 hours ago






  • 1




    @Artyer check virgesmith's comment on the question. I would suggest fixing the exception safety in your answer for a complete example. François's comment after it has the solution.
    – eerorika
    5 hours ago








  • 2




    There is no destructor for std::byte so no non static member is being called. std::byte is defined as enum class byte : unsigned char {} ; so it is just a char
    – NathanOliver
    4 hours ago
















13














This program is not well defined.



The rule is that if a type has a trivial destructor (See this), you don't need to call it. So, this:



return std::shared_ptr<T>(new (memory.release()) T());


is almost correct. It omits the destructor of the sizeof(T) std::bytes, which is fine, constructs a new T in the memory, which is fine, and then when the shared_ptr is ready to delete, it calls delete this->get();, which is wrong. That first deconstructs a T, but then it deallocates a T instead of a std::byte, which will probably (undefined) not work.



C++ standard §8.5.2.4p8 [expr.new]




A new-expression may obtain storage for the object by calling an allocation function. [...] If the allocated type is an array type, the allocation function's name is operator new.




(All those "may"s are because implementations are allowed to merge adjacent new expressions and only call operator new for one of them, but this isn't the case as new only happens once (In make_unique))



And part 11 of the same section:




When a new-expression calls an allocation function and that allocation has not been extended, the new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array. For arrays of char, unsigned char, and std::byte, the difference between the result of the new-expression and the address returned by the
allocation function shall be an integral multiple of the strictest fundamental alignment requirement (6.6.5) of any object type whose size is no greater than the size of the array being created. [Note: Because allocation
functions are assumed to return pointers to storage that is appropriately aligned for objects of any type with fundamental alignment, this constraint on array allocation overhead permits the common idiom of allocating
character arrays into which objects of other types will later be placed. — end note ]




If you read §21.6.2 [new.delete.array], you see that the default operator new and operator delete do the exact same things as operator new and operator delete, the problem is we don't know the size passed to it, and it is probably more than what delete ((T*) object) calls (to store the size).



Looking at what delete-expressions do:



§8.5.2.5p8 [expr.delete]




[...] delete-expression will invoke the destructor (if any) for [...] the elements of the array being deleted




p7.1




If the allocation call for the new-expression for the object to be deleted was not omitted [...], the delete-expression shall call a deallocation function (6.6.4.4.2). The value returned from the allocation call of the new-expression shall be passed as the first argument to the deallocation function.




Since std::byte does not have a destructor, we can safely call delete, as it will not do anything other than call the deallocate function (operator delete). We just have to reinterpret it back to std::byte*, and we will get back what new returned.



Another problem is that there is a memory leak if the constructor of T throws. A simple fix is to placement new while the memory is still owned by the std::unique_ptr, so even if it does throw it will call delete properly.



T* ptr = new (memory.get()) T();
memory.release();
return std::shared_ptr<T>(ptr, (T* ptr) {
ptr->~T();
delete reinterpret_cast<std::byte*>(ptr);
});


The first placement new ends the lifetime of the sizeof(T) std::bytes and starts the lifetime of a new T object at the same address, as according to §6.6.3p5 [basic.life]




A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. [...]




Then when it is being deleted, the lifetime of T ends by an explicit call of the destructor, and then according to the above, the delete-expression deallocates the storage.





This leads to the question of:



What if the storage class wasn't std::byte, and was not trivially destructible? Like, for example, we were using a non-trivial union as the storage.



Calling delete reinterpret_cast<T*>(ptr) would call the destructor on something that is not an object. This is clearly undefined behaviour, and is according to §6.6.3p6 [basic.life]




Before the lifetime of an object has started but after the storage which the object will occupy has been allocated [...], any pointer that represents the address of the storage location where the object will be or
was located may be used but only in limited ways. [...] The program has undefined behavior if: the object will be or was of a class type with a non-trivial destructor and the pointer is used as the operand of a delete-expression




So to use it like above, we have to construct it just to destruct it again.



The default constructor probably works fine. The usual semantics are "create an object that can be destructed", which is exactly what we want. Use std::uninitialized_default_construct_n to construct them all to then immediately destruct them:



    // Assuming we called `new StorageClass[n]` to allocate
ptr->~T();
auto* as_storage = reinterpret_cast<StorageClass*>(ptr);
std::uninitialized_default_construct_n(as_storage, n);
delete as_storage;


We can also call operator new and operator delete ourselves:



static void byte_deleter(std::byte* ptr) {
return ::operator delete(reinterpret_cast<void*>(ptr));
}

auto non_zero_memory(std::size_t size)
{
constexpr std::byte non_zero = static_cast<std::byte>(0xC5);

auto memory = std::unique_ptr<std::byte, void(*)(std::byte*)>(
reinterpret_cast<std::byte*>(::operator new(size)),
&::byte_deleter
);
std::fill(memory.get(), memory.get()+size, non_zero);
return memory;
}

template <class T>
auto on_non_zero_memory()
{
auto memory = non_zero_memory(sizeof(T));
T* ptr = new (memory.get()) T();
memory.release();
return std::shared_ptr<T>(ptr, (T* ptr) {
ptr->~T();
::operator delete(ptr, sizeof(T));
// ^~~~~~~~~ optional
});
}


But this looks a lot like std::malloc and std::free.



A third solution might be to use std::aligned_storage as the type given to new, and have the deleter work as with std::byte because the aligned storage is a trivial aggregate.






share|improve this answer























  • Alignment is only needed for over-aligned types though.
    – eerorika
    5 hours ago






  • 2




    @eerorika I thought it was UB to access (e.g) an int with alignment of 4 if it was not on a 4-byte boundary?
    – Artyer
    5 hours ago






  • 3




    Since this is a language lawyer question, it would be nice to have some form of authoritative source backing up the claims made.
    – François Andrieux
    5 hours ago






  • 1




    @Artyer check virgesmith's comment on the question. I would suggest fixing the exception safety in your answer for a complete example. François's comment after it has the solution.
    – eerorika
    5 hours ago








  • 2




    There is no destructor for std::byte so no non static member is being called. std::byte is defined as enum class byte : unsigned char {} ; so it is just a char
    – NathanOliver
    4 hours ago














13












13








13






This program is not well defined.



The rule is that if a type has a trivial destructor (See this), you don't need to call it. So, this:



return std::shared_ptr<T>(new (memory.release()) T());


is almost correct. It omits the destructor of the sizeof(T) std::bytes, which is fine, constructs a new T in the memory, which is fine, and then when the shared_ptr is ready to delete, it calls delete this->get();, which is wrong. That first deconstructs a T, but then it deallocates a T instead of a std::byte, which will probably (undefined) not work.



C++ standard §8.5.2.4p8 [expr.new]




A new-expression may obtain storage for the object by calling an allocation function. [...] If the allocated type is an array type, the allocation function's name is operator new.




(All those "may"s are because implementations are allowed to merge adjacent new expressions and only call operator new for one of them, but this isn't the case as new only happens once (In make_unique))



And part 11 of the same section:




When a new-expression calls an allocation function and that allocation has not been extended, the new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array. For arrays of char, unsigned char, and std::byte, the difference between the result of the new-expression and the address returned by the
allocation function shall be an integral multiple of the strictest fundamental alignment requirement (6.6.5) of any object type whose size is no greater than the size of the array being created. [Note: Because allocation
functions are assumed to return pointers to storage that is appropriately aligned for objects of any type with fundamental alignment, this constraint on array allocation overhead permits the common idiom of allocating
character arrays into which objects of other types will later be placed. — end note ]




If you read §21.6.2 [new.delete.array], you see that the default operator new and operator delete do the exact same things as operator new and operator delete, the problem is we don't know the size passed to it, and it is probably more than what delete ((T*) object) calls (to store the size).



Looking at what delete-expressions do:



§8.5.2.5p8 [expr.delete]




[...] delete-expression will invoke the destructor (if any) for [...] the elements of the array being deleted




p7.1




If the allocation call for the new-expression for the object to be deleted was not omitted [...], the delete-expression shall call a deallocation function (6.6.4.4.2). The value returned from the allocation call of the new-expression shall be passed as the first argument to the deallocation function.




Since std::byte does not have a destructor, we can safely call delete, as it will not do anything other than call the deallocate function (operator delete). We just have to reinterpret it back to std::byte*, and we will get back what new returned.



Another problem is that there is a memory leak if the constructor of T throws. A simple fix is to placement new while the memory is still owned by the std::unique_ptr, so even if it does throw it will call delete properly.



T* ptr = new (memory.get()) T();
memory.release();
return std::shared_ptr<T>(ptr, (T* ptr) {
ptr->~T();
delete reinterpret_cast<std::byte*>(ptr);
});


The first placement new ends the lifetime of the sizeof(T) std::bytes and starts the lifetime of a new T object at the same address, as according to §6.6.3p5 [basic.life]




A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. [...]




Then when it is being deleted, the lifetime of T ends by an explicit call of the destructor, and then according to the above, the delete-expression deallocates the storage.





This leads to the question of:



What if the storage class wasn't std::byte, and was not trivially destructible? Like, for example, we were using a non-trivial union as the storage.



Calling delete reinterpret_cast<T*>(ptr) would call the destructor on something that is not an object. This is clearly undefined behaviour, and is according to §6.6.3p6 [basic.life]




Before the lifetime of an object has started but after the storage which the object will occupy has been allocated [...], any pointer that represents the address of the storage location where the object will be or
was located may be used but only in limited ways. [...] The program has undefined behavior if: the object will be or was of a class type with a non-trivial destructor and the pointer is used as the operand of a delete-expression




So to use it like above, we have to construct it just to destruct it again.



The default constructor probably works fine. The usual semantics are "create an object that can be destructed", which is exactly what we want. Use std::uninitialized_default_construct_n to construct them all to then immediately destruct them:



    // Assuming we called `new StorageClass[n]` to allocate
ptr->~T();
auto* as_storage = reinterpret_cast<StorageClass*>(ptr);
std::uninitialized_default_construct_n(as_storage, n);
delete as_storage;


We can also call operator new and operator delete ourselves:



static void byte_deleter(std::byte* ptr) {
return ::operator delete(reinterpret_cast<void*>(ptr));
}

auto non_zero_memory(std::size_t size)
{
constexpr std::byte non_zero = static_cast<std::byte>(0xC5);

auto memory = std::unique_ptr<std::byte, void(*)(std::byte*)>(
reinterpret_cast<std::byte*>(::operator new(size)),
&::byte_deleter
);
std::fill(memory.get(), memory.get()+size, non_zero);
return memory;
}

template <class T>
auto on_non_zero_memory()
{
auto memory = non_zero_memory(sizeof(T));
T* ptr = new (memory.get()) T();
memory.release();
return std::shared_ptr<T>(ptr, (T* ptr) {
ptr->~T();
::operator delete(ptr, sizeof(T));
// ^~~~~~~~~ optional
});
}


But this looks a lot like std::malloc and std::free.



A third solution might be to use std::aligned_storage as the type given to new, and have the deleter work as with std::byte because the aligned storage is a trivial aggregate.






share|improve this answer














This program is not well defined.



The rule is that if a type has a trivial destructor (See this), you don't need to call it. So, this:



return std::shared_ptr<T>(new (memory.release()) T());


is almost correct. It omits the destructor of the sizeof(T) std::bytes, which is fine, constructs a new T in the memory, which is fine, and then when the shared_ptr is ready to delete, it calls delete this->get();, which is wrong. That first deconstructs a T, but then it deallocates a T instead of a std::byte, which will probably (undefined) not work.



C++ standard §8.5.2.4p8 [expr.new]




A new-expression may obtain storage for the object by calling an allocation function. [...] If the allocated type is an array type, the allocation function's name is operator new.




(All those "may"s are because implementations are allowed to merge adjacent new expressions and only call operator new for one of them, but this isn't the case as new only happens once (In make_unique))



And part 11 of the same section:




When a new-expression calls an allocation function and that allocation has not been extended, the new-expression passes the amount of space requested to the allocation function as the first argument of type std::size_t. That argument shall be no less than the size of the object being created; it may be greater than the size of the object being created only if the object is an array. For arrays of char, unsigned char, and std::byte, the difference between the result of the new-expression and the address returned by the
allocation function shall be an integral multiple of the strictest fundamental alignment requirement (6.6.5) of any object type whose size is no greater than the size of the array being created. [Note: Because allocation
functions are assumed to return pointers to storage that is appropriately aligned for objects of any type with fundamental alignment, this constraint on array allocation overhead permits the common idiom of allocating
character arrays into which objects of other types will later be placed. — end note ]




If you read §21.6.2 [new.delete.array], you see that the default operator new and operator delete do the exact same things as operator new and operator delete, the problem is we don't know the size passed to it, and it is probably more than what delete ((T*) object) calls (to store the size).



Looking at what delete-expressions do:



§8.5.2.5p8 [expr.delete]




[...] delete-expression will invoke the destructor (if any) for [...] the elements of the array being deleted




p7.1




If the allocation call for the new-expression for the object to be deleted was not omitted [...], the delete-expression shall call a deallocation function (6.6.4.4.2). The value returned from the allocation call of the new-expression shall be passed as the first argument to the deallocation function.




Since std::byte does not have a destructor, we can safely call delete, as it will not do anything other than call the deallocate function (operator delete). We just have to reinterpret it back to std::byte*, and we will get back what new returned.



Another problem is that there is a memory leak if the constructor of T throws. A simple fix is to placement new while the memory is still owned by the std::unique_ptr, so even if it does throw it will call delete properly.



T* ptr = new (memory.get()) T();
memory.release();
return std::shared_ptr<T>(ptr, (T* ptr) {
ptr->~T();
delete reinterpret_cast<std::byte*>(ptr);
});


The first placement new ends the lifetime of the sizeof(T) std::bytes and starts the lifetime of a new T object at the same address, as according to §6.6.3p5 [basic.life]




A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. [...]




Then when it is being deleted, the lifetime of T ends by an explicit call of the destructor, and then according to the above, the delete-expression deallocates the storage.





This leads to the question of:



What if the storage class wasn't std::byte, and was not trivially destructible? Like, for example, we were using a non-trivial union as the storage.



Calling delete reinterpret_cast<T*>(ptr) would call the destructor on something that is not an object. This is clearly undefined behaviour, and is according to §6.6.3p6 [basic.life]




Before the lifetime of an object has started but after the storage which the object will occupy has been allocated [...], any pointer that represents the address of the storage location where the object will be or
was located may be used but only in limited ways. [...] The program has undefined behavior if: the object will be or was of a class type with a non-trivial destructor and the pointer is used as the operand of a delete-expression




So to use it like above, we have to construct it just to destruct it again.



The default constructor probably works fine. The usual semantics are "create an object that can be destructed", which is exactly what we want. Use std::uninitialized_default_construct_n to construct them all to then immediately destruct them:



    // Assuming we called `new StorageClass[n]` to allocate
ptr->~T();
auto* as_storage = reinterpret_cast<StorageClass*>(ptr);
std::uninitialized_default_construct_n(as_storage, n);
delete as_storage;


We can also call operator new and operator delete ourselves:



static void byte_deleter(std::byte* ptr) {
return ::operator delete(reinterpret_cast<void*>(ptr));
}

auto non_zero_memory(std::size_t size)
{
constexpr std::byte non_zero = static_cast<std::byte>(0xC5);

auto memory = std::unique_ptr<std::byte, void(*)(std::byte*)>(
reinterpret_cast<std::byte*>(::operator new(size)),
&::byte_deleter
);
std::fill(memory.get(), memory.get()+size, non_zero);
return memory;
}

template <class T>
auto on_non_zero_memory()
{
auto memory = non_zero_memory(sizeof(T));
T* ptr = new (memory.get()) T();
memory.release();
return std::shared_ptr<T>(ptr, (T* ptr) {
ptr->~T();
::operator delete(ptr, sizeof(T));
// ^~~~~~~~~ optional
});
}


But this looks a lot like std::malloc and std::free.



A third solution might be to use std::aligned_storage as the type given to new, and have the deleter work as with std::byte because the aligned storage is a trivial aggregate.







share|improve this answer














share|improve this answer



share|improve this answer








edited 1 hour ago

























answered 5 hours ago









ArtyerArtyer

4,106727




4,106727












  • Alignment is only needed for over-aligned types though.
    – eerorika
    5 hours ago






  • 2




    @eerorika I thought it was UB to access (e.g) an int with alignment of 4 if it was not on a 4-byte boundary?
    – Artyer
    5 hours ago






  • 3




    Since this is a language lawyer question, it would be nice to have some form of authoritative source backing up the claims made.
    – François Andrieux
    5 hours ago






  • 1




    @Artyer check virgesmith's comment on the question. I would suggest fixing the exception safety in your answer for a complete example. François's comment after it has the solution.
    – eerorika
    5 hours ago








  • 2




    There is no destructor for std::byte so no non static member is being called. std::byte is defined as enum class byte : unsigned char {} ; so it is just a char
    – NathanOliver
    4 hours ago


















  • Alignment is only needed for over-aligned types though.
    – eerorika
    5 hours ago






  • 2




    @eerorika I thought it was UB to access (e.g) an int with alignment of 4 if it was not on a 4-byte boundary?
    – Artyer
    5 hours ago






  • 3




    Since this is a language lawyer question, it would be nice to have some form of authoritative source backing up the claims made.
    – François Andrieux
    5 hours ago






  • 1




    @Artyer check virgesmith's comment on the question. I would suggest fixing the exception safety in your answer for a complete example. François's comment after it has the solution.
    – eerorika
    5 hours ago








  • 2




    There is no destructor for std::byte so no non static member is being called. std::byte is defined as enum class byte : unsigned char {} ; so it is just a char
    – NathanOliver
    4 hours ago
















Alignment is only needed for over-aligned types though.
– eerorika
5 hours ago




Alignment is only needed for over-aligned types though.
– eerorika
5 hours ago




2




2




@eerorika I thought it was UB to access (e.g) an int with alignment of 4 if it was not on a 4-byte boundary?
– Artyer
5 hours ago




@eerorika I thought it was UB to access (e.g) an int with alignment of 4 if it was not on a 4-byte boundary?
– Artyer
5 hours ago




3




3




Since this is a language lawyer question, it would be nice to have some form of authoritative source backing up the claims made.
– François Andrieux
5 hours ago




Since this is a language lawyer question, it would be nice to have some form of authoritative source backing up the claims made.
– François Andrieux
5 hours ago




1




1




@Artyer check virgesmith's comment on the question. I would suggest fixing the exception safety in your answer for a complete example. François's comment after it has the solution.
– eerorika
5 hours ago






@Artyer check virgesmith's comment on the question. I would suggest fixing the exception safety in your answer for a complete example. François's comment after it has the solution.
– eerorika
5 hours ago






2




2




There is no destructor for std::byte so no non static member is being called. std::byte is defined as enum class byte : unsigned char {} ; so it is just a char
– NathanOliver
4 hours ago




There is no destructor for std::byte so no non static member is being called. std::byte is defined as enum class byte : unsigned char {} ; so it is just a char
– NathanOliver
4 hours ago













8














std::shared_ptr<T>(new (memory.release()) T())


Is undefined behavior. The memory that was acquired by memory was for a std::byte but the shared_ptr's deleter is doing to call delete on a pointer to T. Since the pointer no longer has the same type you can't call delete on it per [expr.delete]/2




In a single-object delete expression, the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject representing a base class of such an object. If not, the behavior is undefined.




You would have to provide the shared_ptr with a custom deleter that destroys T and then casts the pointer back to its source type and call delete on that.





It should also be noted that new (memory.release()) T() itself will be undefined if memory allocated a type that has non trivial destruction. You would have to call the destructor on the pointer from memory.release() first before you reuse it's memory.






share|improve this answer



















  • 1




    @FrançoisAndrieux Seems the standard requires the type of the thing you delete to match the thing you got back from new, regardless. Which seems like a bit of a bastard actually if that thing has been replaced.
    – Lightness Races in Orbit
    5 hours ago








  • 1




    This is true new ... delete is a problem. I though of the other points but not to that one. Nice catch.
    – YSC
    5 hours ago






  • 2




    Would it be sufficient to have a deleter that does ptr->~T(); delete ptr;? Edit : Might have to cast the pointer to std::byte* first, or maybe void* since you don't want a destructor call anyway. Edit 2 : deleteing a void* might be UB if it compiles at all.
    – François Andrieux
    5 hours ago








  • 1




    @FrançoisAndrieux maybe ptr->~T(); delete reinterpret_cast<std::byte*>(ptr);?
    – YSC
    5 hours ago






  • 1




    @NathanOliver Seems reasonable, though I think the lifetime of the array is over once you placement new over it. I'm not sure whether or not that's a problem. If it was an array of a more complex type, you would have needed to call the destructors ahead of the placement new so it would definitively not be OK (delete would destroy them again). Not sure if trivial types have an exemption for this.
    – François Andrieux
    5 hours ago


















8














std::shared_ptr<T>(new (memory.release()) T())


Is undefined behavior. The memory that was acquired by memory was for a std::byte but the shared_ptr's deleter is doing to call delete on a pointer to T. Since the pointer no longer has the same type you can't call delete on it per [expr.delete]/2




In a single-object delete expression, the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject representing a base class of such an object. If not, the behavior is undefined.




You would have to provide the shared_ptr with a custom deleter that destroys T and then casts the pointer back to its source type and call delete on that.





It should also be noted that new (memory.release()) T() itself will be undefined if memory allocated a type that has non trivial destruction. You would have to call the destructor on the pointer from memory.release() first before you reuse it's memory.






share|improve this answer



















  • 1




    @FrançoisAndrieux Seems the standard requires the type of the thing you delete to match the thing you got back from new, regardless. Which seems like a bit of a bastard actually if that thing has been replaced.
    – Lightness Races in Orbit
    5 hours ago








  • 1




    This is true new ... delete is a problem. I though of the other points but not to that one. Nice catch.
    – YSC
    5 hours ago






  • 2




    Would it be sufficient to have a deleter that does ptr->~T(); delete ptr;? Edit : Might have to cast the pointer to std::byte* first, or maybe void* since you don't want a destructor call anyway. Edit 2 : deleteing a void* might be UB if it compiles at all.
    – François Andrieux
    5 hours ago








  • 1




    @FrançoisAndrieux maybe ptr->~T(); delete reinterpret_cast<std::byte*>(ptr);?
    – YSC
    5 hours ago






  • 1




    @NathanOliver Seems reasonable, though I think the lifetime of the array is over once you placement new over it. I'm not sure whether or not that's a problem. If it was an array of a more complex type, you would have needed to call the destructors ahead of the placement new so it would definitively not be OK (delete would destroy them again). Not sure if trivial types have an exemption for this.
    – François Andrieux
    5 hours ago
















8












8








8






std::shared_ptr<T>(new (memory.release()) T())


Is undefined behavior. The memory that was acquired by memory was for a std::byte but the shared_ptr's deleter is doing to call delete on a pointer to T. Since the pointer no longer has the same type you can't call delete on it per [expr.delete]/2




In a single-object delete expression, the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject representing a base class of such an object. If not, the behavior is undefined.




You would have to provide the shared_ptr with a custom deleter that destroys T and then casts the pointer back to its source type and call delete on that.





It should also be noted that new (memory.release()) T() itself will be undefined if memory allocated a type that has non trivial destruction. You would have to call the destructor on the pointer from memory.release() first before you reuse it's memory.






share|improve this answer














std::shared_ptr<T>(new (memory.release()) T())


Is undefined behavior. The memory that was acquired by memory was for a std::byte but the shared_ptr's deleter is doing to call delete on a pointer to T. Since the pointer no longer has the same type you can't call delete on it per [expr.delete]/2




In a single-object delete expression, the value of the operand of delete may be a null pointer value, a pointer to a non-array object created by a previous new-expression, or a pointer to a subobject representing a base class of such an object. If not, the behavior is undefined.




You would have to provide the shared_ptr with a custom deleter that destroys T and then casts the pointer back to its source type and call delete on that.





It should also be noted that new (memory.release()) T() itself will be undefined if memory allocated a type that has non trivial destruction. You would have to call the destructor on the pointer from memory.release() first before you reuse it's memory.







share|improve this answer














share|improve this answer



share|improve this answer








edited 4 hours ago

























answered 5 hours ago









NathanOliverNathanOliver

87.5k15120180




87.5k15120180








  • 1




    @FrançoisAndrieux Seems the standard requires the type of the thing you delete to match the thing you got back from new, regardless. Which seems like a bit of a bastard actually if that thing has been replaced.
    – Lightness Races in Orbit
    5 hours ago








  • 1




    This is true new ... delete is a problem. I though of the other points but not to that one. Nice catch.
    – YSC
    5 hours ago






  • 2




    Would it be sufficient to have a deleter that does ptr->~T(); delete ptr;? Edit : Might have to cast the pointer to std::byte* first, or maybe void* since you don't want a destructor call anyway. Edit 2 : deleteing a void* might be UB if it compiles at all.
    – François Andrieux
    5 hours ago








  • 1




    @FrançoisAndrieux maybe ptr->~T(); delete reinterpret_cast<std::byte*>(ptr);?
    – YSC
    5 hours ago






  • 1




    @NathanOliver Seems reasonable, though I think the lifetime of the array is over once you placement new over it. I'm not sure whether or not that's a problem. If it was an array of a more complex type, you would have needed to call the destructors ahead of the placement new so it would definitively not be OK (delete would destroy them again). Not sure if trivial types have an exemption for this.
    – François Andrieux
    5 hours ago
















  • 1




    @FrançoisAndrieux Seems the standard requires the type of the thing you delete to match the thing you got back from new, regardless. Which seems like a bit of a bastard actually if that thing has been replaced.
    – Lightness Races in Orbit
    5 hours ago








  • 1




    This is true new ... delete is a problem. I though of the other points but not to that one. Nice catch.
    – YSC
    5 hours ago






  • 2




    Would it be sufficient to have a deleter that does ptr->~T(); delete ptr;? Edit : Might have to cast the pointer to std::byte* first, or maybe void* since you don't want a destructor call anyway. Edit 2 : deleteing a void* might be UB if it compiles at all.
    – François Andrieux
    5 hours ago








  • 1




    @FrançoisAndrieux maybe ptr->~T(); delete reinterpret_cast<std::byte*>(ptr);?
    – YSC
    5 hours ago






  • 1




    @NathanOliver Seems reasonable, though I think the lifetime of the array is over once you placement new over it. I'm not sure whether or not that's a problem. If it was an array of a more complex type, you would have needed to call the destructors ahead of the placement new so it would definitively not be OK (delete would destroy them again). Not sure if trivial types have an exemption for this.
    – François Andrieux
    5 hours ago










1




1




@FrançoisAndrieux Seems the standard requires the type of the thing you delete to match the thing you got back from new, regardless. Which seems like a bit of a bastard actually if that thing has been replaced.
– Lightness Races in Orbit
5 hours ago






@FrançoisAndrieux Seems the standard requires the type of the thing you delete to match the thing you got back from new, regardless. Which seems like a bit of a bastard actually if that thing has been replaced.
– Lightness Races in Orbit
5 hours ago






1




1




This is true new ... delete is a problem. I though of the other points but not to that one. Nice catch.
– YSC
5 hours ago




This is true new ... delete is a problem. I though of the other points but not to that one. Nice catch.
– YSC
5 hours ago




2




2




Would it be sufficient to have a deleter that does ptr->~T(); delete ptr;? Edit : Might have to cast the pointer to std::byte* first, or maybe void* since you don't want a destructor call anyway. Edit 2 : deleteing a void* might be UB if it compiles at all.
– François Andrieux
5 hours ago






Would it be sufficient to have a deleter that does ptr->~T(); delete ptr;? Edit : Might have to cast the pointer to std::byte* first, or maybe void* since you don't want a destructor call anyway. Edit 2 : deleteing a void* might be UB if it compiles at all.
– François Andrieux
5 hours ago






1




1




@FrançoisAndrieux maybe ptr->~T(); delete reinterpret_cast<std::byte*>(ptr);?
– YSC
5 hours ago




@FrançoisAndrieux maybe ptr->~T(); delete reinterpret_cast<std::byte*>(ptr);?
– YSC
5 hours ago




1




1




@NathanOliver Seems reasonable, though I think the lifetime of the array is over once you placement new over it. I'm not sure whether or not that's a problem. If it was an array of a more complex type, you would have needed to call the destructors ahead of the placement new so it would definitively not be OK (delete would destroy them again). Not sure if trivial types have an exemption for this.
– François Andrieux
5 hours ago






@NathanOliver Seems reasonable, though I think the lifetime of the array is over once you placement new over it. I'm not sure whether or not that's a problem. If it was an array of a more complex type, you would have needed to call the destructors ahead of the placement new so it would definitively not be OK (delete would destroy them again). Not sure if trivial types have an exemption for this.
– François Andrieux
5 hours ago




















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.





Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


Please pay close attention to the following guidance:


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f54095233%2fis-it-ok-to-make-a-placement-new-on-memory-managed-by-a-smart-pointer%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Hivernacle

Fluorita

Hulsita