While reviewing some testing code, I have come across this situation:
Suppose we have these two classes:
class Project
{
public:
Project(std::string path);
~Project;
// [many more functions]
};
class ResourceManager
{
public:
ResourceManager(Project &proj);
virtual ~ResourceManager();
// [many more functions]
// [notably no getProject()]
};
In the test code, there exists a 'fake' version of the ResourceManager:
class FakeResourceManager : public ResourceManager
{
std::unique_ptr<Project> project_;
Project* makeProject()
{
project_= std::make_unique<Project>("/some/path"); // oops, accessing non-initialized member
return project_.get();
};
public:
FakeResourceManager()
: ResourceManager(*makeProject())
{}
virtual ~FakeResourceManager(); // defaulted in cpp
void markResourceAsDirty(); // 'extension' of base functionality for testing
};
This of course doesn't work, since the project_ member is not initialized yet when makeProject is called, which the address sanitizer catches at runtime.
I reworked this initialization code, but I am unsure if it's now free of any UB and does the expected thing:
class FakeResourceManager : public ResourceManager
{
std::unique_ptr<Project> project_;
FakeResourceManager(std::unique_ptr<Project> project)
: ResourceManager(*project.get())
, project_(std::move(project)) //(1)
{}
public:
FakeResourceManager()
: FakeResourceManager(std::make_unique<Project>("/some/path"))
{}
virtual ~FakeResourceManager(); // defaulted in cpp
void markResourceAsDirty(); // 'extension' of base functionality for testing
}
Points I considered:
- The
std::move(1) of theunique_ptrshould not invalidate the pointer previously returned byget(). The moved-to member should correctly delete the Project on destruction. - If the
ResourceManagerdestructor uses the passed project reference it would be UB, since that object already got deleted by the derived class dtor. Since it doesn't, it should be ok to have a dangling reference here (or is this still UB?) - The FakeResourceManager needs to be a derived class, since it accesses protected members in the 'extension' method.
- A static factory function would work much the same way, but I see no benefit over this version.
Does this solution still contain UB (or some other logic error), or is this safe?
FRM()->FRM(unique_ptr)->FRM(unique_ptr, Project&))ResourceManager's destructor)