Why is my struct destructed twice with `std::variant` and `std::monostate`?
Asked Answered
M

2

24

I am trying to learn std::variant. I do not understand why in this example, where I prefer not to initialize ab yet, and I use std::monostate for that, the class A gets constructed once, but destructed twice. What is happening?

#include <iostream>
#include <variant>

struct A
{
    A() { std::cout << "Constructing A\n"; }
    ~A() { std::cout << "Destructing A\n"; }
};


struct B
{
    B() { std::cout << "Constructing B\n"; }
    ~B() { std::cout << "Destructing B\n"; }
};


int main()
{
    std::variant<std::monostate, A, B> ab;
    ab = A();
}

Running this example gives the output below.

Constructing A
Destructing A
Destructing A
Madelenemadelin answered 15/3, 2023 at 7:11 Comment(4)
Presumably because at some point it's copy constructed. Have you tried instrumenting the copy constructor?Landpoor
ab = A() constructs A, then moves it to the variant. What you are thinking of is ab.emplace<A>()Serous
godbolt.org/z/7eGTTvorcUyekawa
See this answer in the dupe: Extra destructor called in vector of class object. Also destructor is called twice for variant-based implementationSpirogyra
W
38

This line:

ab = A();

Is creating a temporary A object, and then moving it into ab.
You can observe this by adding copy/move constructors and assignment operators:

#include <iostream>
#include <variant>

struct A
{
    A()                    { std::cout << "Constructing A\n"; }
    A(A const&)            { std::cout << "Copy constructing A\n"; }
    A(A&&)                 { std::cout << "Move constructing A\n"; }
    A& operator=(A const&) { std::cout << "Copy assigning A\n"; return *this; }
    A& operator=(A&&)      { std::cout << "Move assigning A\n"; return *this; }
    ~A()                   { std::cout << "Destructing A\n"; }
};

struct B
{
};

int main()
{
    std::variant<std::monostate, A, B> ab;
    ab = A();
}

Output:

Constructing A
Move constructing A
Destructing A
Destructing A

You can avoid the copy/move, by using std::variant::emplace.
If you replace the above mentioned line with:

ab.emplace<A>();

The output should become:

Constructing A
Destructing A
Winny answered 15/3, 2023 at 7:22 Comment(0)
C
15

I added 2 lines to your code.

struct A
{
    A() { std::cout << "Constructing A\n"; }
    A(const A& a) { std::cout << "Copy  constructing A\n"; }  // <== here
    ~A() { std::cout << "Destructing A\n"; }
};


struct B
{
    B() { std::cout << "Constructing B\n"; }
    B(const B& a) { std::cout << "Copy  constructing B\n"; }   // <== and here
    ~B() { std::cout << "Destructing B\n"; }
};

Then we can get such result:

Constructing A
Copy  constructing A
Destructing A
Destructing A

I am not familiar with std::variant, I just hope above experiment could take you closer to the real answer. :)

Chargeable answered 15/3, 2023 at 7:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.