#include <memory>
#include <functional>
#include <iostream>
struct TestStruct {
int a = 100;
};
struct StructDeleter {
void operator()(TestStruct *ptr) const {
delete ptr;
}
};
std::unique_ptr<TestStruct, StructDeleter> MakeNewStruct() {
std::unique_ptr<TestStruct> st(new TestStruct());
std::unique_ptr<TestStruct, StructDeleter> customDeleterSt(std::move(st));
std::cout << customDeleterSt->a << std::endl;
return customDeleterSt;
}
int main() {
auto a = MakeNewStruct();
std::cout << a->a << std::endl;
return 0;
}
The above code can not be compiled, how to move st
to customDeleterSt
?
I get a st
unique_ptr from the st
creation interface, and I use the custom deleter to hide the implementation of st
from my users, so how to move a unique_ptr without custom deleter to a unique_tr with custom deleter?
Thanks for any help!
CodePudding user response:
As noted in the comments, the brute-force way is to have the source .release()
to the constructor of the destination. However there is a much more elegant solution (imho):
Add an implicit conversion from std::default_delete<TestStruct>
to StructDeleter
:
struct StructDeleter {
StructDeleter(std::default_delete<TestStruct>) {} // Add this
void operator()(TestStruct *ptr) const {
delete ptr;
}
};
Now the code works with the existing move construction syntax.
Whether this is done via the converting constructor, or .release()
, if StructDeleter
can't (for whatever reasons) properly delete a pointer that std::default_delete
handles, this will result in undefined behavior. It is only because StructDeleter
calls delete ptr
that either of these techniques works.
As written, TestStruct
does not need to be a complete type at the point of delete
. However should TestStruct
acquire a non-trivial destructor, you will also need to ensure that for any code that calls StructDeleter::operator()(TestStruct*)
, that TestStruct
is a complete type, otherwise you're back into UB territory again. This assurance is one of the things that std::default_delete<TestStruct>
does for you and that StructDeleter
(as written) does not.
If it is intended to keep ~TestStruct()
trivial, it would be a good idea to add:
static_assert(std::is_trivially_destructible<TestStruct>::value);