std::optional<T> appears to destroy T twice when leaving its scope. Why?

Advertisements

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.

>Solution :

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.

Leave a ReplyCancel reply