Open wesleywong1995 opened 1 year ago
Thanks for reporting this! Were you just running https://github.com/taichi-dev/cpp-training-season1/blob/main/reflection/src/main.cpp?
There's nothing fancy in ~RawTypeDescriptorBuilder()
: https://github.com/taichi-dev/cpp-training-season1/blob/bfe7b2f35a4517635545aea9cf74a0f7097dbaa3/reflection/src/reflect.cpp#L13-L15. If you print desc_
's address before L14, is it already nullptr
?
It is a common bug about move constructor and copy assignment operator. In these constructor, std::unique_ptr<T>
member is moved from rhs to lhs, so it is empty in rhs.
In code, AddClass
return a instance of TypeDescriptorBuilder<T>
, it calls move constructor.
// reflect.hpp
template <typename T>
details::TypeDescriptorBuilder<T> AddClass(const std::string &name) {
details::TypeDescriptorBuilder<T> b{name};
return b;
}
Definition of TypeDescriptorBuilder<T>
doesn't implement move constructor, so the default one is called.
// reflect.hpp
template <typename T>
class TypeDescriptorBuilder {
...
private:
RawTypeDescriptorBuilder raw_builder_;
};
Default move ctor of TypeDescriptorBuilder<T>
calls move constructor of RawTypeDescriptorBuilder
.
// reflect.hpp
class RawTypeDescriptorBuilder {
...
RawTypeDescriptorBuilder(RawTypeDescriptorBuilder&& rhs) = default;
RawTypeDescriptorBuilder& operator=(RawTypeDescriptorBuilder&& rhs) = default;
...
private:
std::unique_ptr<TypeDescriptor> desc_{nullptr};
};
Default move ctor of RawTypeDescriptorBuilder
moves std::unique_ptr<TypeDescriptor> desc_
.
Now go back to details::TypeDescriptorBuilder<T> AddClass(const std::string &name)
, after move ctor, the lhs now owns the std::unique_ptr
member, the rhs owns empty.
When the program leaves details::TypeDescriptorBuilder<T> AddClass(const std::string &name)
, the rhs is deleted.
To output the process, modify the code like this:
// reflect.hpp
template <typename V>
TypeDescriptorBuilder &AddMemberVar(const std::string &name, V T::*var) {
std::cout << "AddMemberVar" << std::endl;
raw_builder_.AddMemberVar(name, var);
return *this;
}
// reflect.cpp
RawTypeDescriptorBuilder::~RawTypeDescriptorBuilder() {
std::cout << "~RawTypeDescriptorBuilder()" << std::endl;
std::cout << "(desc_ == nullptr) = " << (desc_ == nullptr) << std::endl;
if(desc_ != nullptr)
Registry::instance().Register(std::move(desc_));
}
The output is
~RawTypeDescriptorBuilder()
(desc_ == nullptr) = 1
AddMemberVar
AddMemberVar
~RawTypeDescriptorBuilder()
(desc_ == nullptr) = 0
Obviously, the fix is
// reflect.cpp
RawTypeDescriptorBuilder::~RawTypeDescriptorBuilder() {
if(desc_ != nullptr)
Registry::instance().Register(std::move(desc_));
}
测试环境,windows, unbuntu wsl。 在RawTypeDescriptorBuilder进行析构注册的时候desc_变量报空,是否是析构顺序的问题?