Use union to defer member variable construction

Viewed 94

I want to defer the construction of my member variable until the constructor's body and I am trying to use union to do that. Until now, it's achieving what I want but I want to ask is there any reason I shouldn't do this?

Example:

#include <iostream>

struct A {
  A() {
    std::cout << "Construct A" << std::endl;
  }
  ~A() {
    std::cout << "Destruct A" << std::endl;
  }
};

struct B {
  A a;
};

template <typename T>
union U {
  char a{};
  T buffer;
  U() {}
  ~U() {
    buffer.~T();
  }
};

struct C {
  U<B> u;
  C() {
    try {
      new (&u.buffer) B();
    } catch (...) {
    }
  }
};

Edit: Add sample usage

2 Answers

One of the reason you should not use the work around is that it does not make sense. Apply try-catch on hole constructor will work well.

struct C {
    A a;
    C() try {
    } catch (...) {
    }
};

std::optional seems to be a great way to do this if you're at C++17.

#include <iostream>
#include <optional>
#include <stdexcept>

struct A {
    A(bool fail = false) {
        std::cout << "Attempting to construct A" << std::endl;
        if (fail) {
            throw std::runtime_error("Failed to construct A");
        }
        else {
            std::cout << "Succeeded in constructing A" << std::endl;
        }
    }

    ~A() {
        std::cout << "Destruct A" << std::endl;
    }
};

struct B {
    std::optional<A> a;

    B(bool fail = false) {
        try {
            a.emplace(fail);
        }
        catch (std::runtime_error& ex) {
            // fall back to a safe construction
            std::cout << "Falling back to safe A construction" << std::endl;
            a.emplace();
        }
    }
};

int main() {
    {
        B b_good; // should be fine
    }

    {
        B B_bad(true); // should catch the exception and fall back
    }
}

output:

Attempting to construct A
Succeeded in constructing A
Destruct A
Attempting to construct A
Failed to construct A

An option that forgoes the size of std::optional is to have the unallocated buffer, but (for type safety) access it through a reference.

#include <iostream>
#include <optional>
#include <stdexcept>

struct A {
    A(bool fail = false) {
        std::cout << "Attempting to construct A" << std::endl;
        if (fail) {
            throw std::runtime_error("Failed to construct A");
        }
        else {
            std::cout << "Succeeded in constructing A" << std::endl;
        }
    }

    ~A() {
        std::cout << "Destruct A" << std::endl;
    }
};

struct B {
    char a_buff_[sizeof(A)];
    A& a_;

    B(bool fail = false) : a_(*reinterpret_cast<A*>(a_buff_)) {
        try {
            new (&a_) A(fail);
        }
        catch (std::runtime_error& ex) {
            std::cout << ex.what() << std::endl;
            std::cout << "Falling back to safe A construction" << std::endl;
            new (&a_) A();
        }
    }

    ~B() { a_.~A(); }

    B(const B& other) : a_(other.a_) {}

    B& operator=(const B& other) {
        a_ = other.a_;
    }
};

int main() {
    {
        B b_good; // should be fine
    }
    
    {
        B b_bad(true); // should catch the exception and fall back
    }
}
Attempting to construct A
Succeeded in constructing A
Destruct A
Attempting to construct A
Failed to construct A
Falling back to safe A construction
Attempting to construct A
Succeeded in constructing A
Destruct A
Related