C++ Some misunderstand about union

Viewed 116

I am new to C++, and I have some questions about how the union works. This is my code :

#include <iostream>
using namespace std;



union S
{

    std::string str;
    int a;

~S() {}
};

int main()
{
    S s {};
    s.str = "hello";

std::cout << s.str << std::endl;
s.a = 3;
std::cout << s.a;
    std::cout << "This is my string : " <<  s.str << std::endl;

}

  1. If I write " S s" instead of " S s{}", i have a error --> use of the deleted function S::S().
  2. if I delete this line "~S() {}", i have a error --> use of the deleted function S::~S().

In this website, https://en.cppreference.com/w/cpp/language/union, it is said that because I have a member (string) with a non default constructor/destructor, it will delete the default constructor and the default destructor of the union S. My question is : Why? I still don't understand why they delete the default constructor/destructor in C++.

  1. Also, I have heard it is important to explicitly call the destructor if i want to switch the string to an integer because it will leads to the memory leak.My question is: Do I need to call the union destructor or the string destructor? In this code,if I need to call the union destructor, the destructor does nothing, so does that means my string won't be ereased?. If I need to call the string destructor, I don't know how to write the string destructor. Thank !

When i run this code, it shows me this :

hello
3
This is my string :

As I have expected, the last sentence "This is my string :" doesn't show me the string "hello" because I have overwrite "s.a = 3". But, it seems s.str is empty. My questions is: Why does s.str is empty. Does it mean that the compiler has called automatically the destructor of my string. Thank!

I know there are alternative like boost or variant, but I still want to understand this.

1 Answers

If I write " S s" instead of " S s{}", i have a error --> use of the deleted function S::S().

S contains a string and this string must be constructed before it can be used. You've gotten around this with aggregate initialization which ensures the first member of the union will be correctly initialized.

Note that if a was the first member, it would be initialized rather than str and s.str = "hello"; exhibits some of that classic Undefined Behaviour action.

You could also satisfy the compiler by adding a constructor that constructed the member you wished to use as the active member. Then it doesn't matter what order. As of C++20 you can use designated initializers, S s{.str=""};, to select which member to initialize and still use aggregate initialization to avoid writing a constructor

if I delete this line "~S() {}", i have a error --> use of the deleted function S::~S().

Just as str must be constructed, you also need to have scaffolding to ensure that it can be destroyed if str is the active member when s is destroyed. This usually requires more than just a union because you need some book-keeping to track the active member. Destroying str when it is not the active member is a fatal mistake.

Also, I have heard it is important to explicitly call the destructor if i want to switch the string to an integer because it will leads to the memory leak.My question is: Do I need to call the union destructor or the string destructor? In this code,if I need to call the union destructor, the destructor does nothing, so does that means my string won't be ereased?. If I need to call the string destructor, I don't know how to write the string destructor. Thank !

Any time you stop using s as a string, before assigning to s.a or when destroying s when s is being used as a string, you need to call the string destructor to end the lifetime of str.

So

s.a = 3;

needs to become

s.str.~string();
s.a = 3;

In addition, any time you want to make str the active member, you need to make sure it is constructed.

std::cout << "This is my string : " << s.str << std::endl;

needs to become

new (&s.str) std::string("I'm Baaaaaack!");
std::cout << "This is my string : " << s.str << std::endl;

and then, because str is the active member we should destroy it before main exits.

s.str.~string();

All bundled up we get,

#include <iostream>
using namespace std;

union S
{

    std::string str;
    int a;

    ~S()
    {
    }
};

int main()
{
    S s{};
    s.str = "hello";

    std::cout << s.str << std::endl;
    s.str.~string();
    s.a = 3;
    std::cout << s.a;

    new (&s.str) std::string("I'm Baaaaaack!");
    std::cout << "This is my string : " << s.str << std::endl;
    s.str.~string();
}
Related