I'm looking to replace some old raw pointer handling for objects (unparented QObjects) that in practice are treated as optional objects. E.g:
if(m_file)
delete m_file;
m_file = new QFile(...);`
Both std::unique_ptr and std::optional seem to be suitable for my needs.
I decided to do some testing first, and came across some strange behaviour with std::optional: The T::dtor appears to be called twice when exiting scope.
https://godbolt.org/z/Yeq887YPh.
struct Loud
{
Loud(std::string name) : m_name{ name } { print("Creating"); }
Loud(const Loud& other) : m_name{ other.m_name } { print("Copying"); }
Loud(Loud&& other) : m_name{ other.m_name }{ print("Moving"); }
~Loud() { print("Destroying"); }
Loud& operator=(const Loud& other){ this->m_name = other.m_name; print("Copy="); return *this;}
Loud& operator=(Loud&& other){ this->m_name = other.m_name; print("Move=");; return *this;}
std::string m_name;
void print(std::string operation) { std::cout << operation << " " << m_name << "\n"; }
};
void optionalTest()
{
std::optional<Loud> opt;
opt = Loud("opt 1");
opt = Loud("opt 2");
}
void uniqueTest()
{
std::unique_ptr<Loud> unique;
unique = std::make_unique<Loud>("unique 1");
unique = std::make_unique<Loud>("unique 2");
}
int main()
{
optionalTest();
std::cout << "\n";
uniqueTest();
}
Which produces the output
Creating opt 1
Moving opt 1
Destroying opt 1
Creating opt 2
Move= opt 2
Destroying opt 2
Destroying opt 2 <-- Whyy??
Creating unique 1
Creating unique 2
Destroying unique 1
Destroying unique 2
I'm a bit worried that the double dtor might cause issues with deallocating resources, so I'm currently more inclined to opt for the unique_ptr.
Edit:
Only the last destruction is the actual std::optional<Loud> opt
object being destroyed. The rest of the destructors are from temporaries.
Its illustrated better by changing the other.m_name
in the move operators (https://godbolt.org/z/3vWfPPY7b).
Output:
Creating opt 1
Moving opt 1
Destroying opt 1 (mvd from)
Creating opt 2
Move= opt 2
Destroying opt 2 (mvd from)
Destroying opt 2
Everything is fine. You have 3 objects being worked on:
a) in std::optional
,
b) temporary opt 1
and
c) temporary opt2
.
Now, referring those to your output:
Creating opt 1 // create b
Moving opt 1 // create a using b
Destroying opt 1 // destroy b
Creating opt 2 // create c
Move= opt 2 // update a using c
Destroying opt 2 // destroy c
Destroying opt 2 // destroy a
std::unique_ptr
doesn't store its own object, only a pointer, so there's no 3rd object in this case.