Используйте объединение, чтобы отложить создание переменных-членов - PullRequest
3 голосов
/ 01 августа 2020

Я хочу отложить создание моей переменной-члена до тела конструктора, и я пытаюсь использовать для этого union. До сих пор он достиг того, чего я хочу, но я хочу спросить, есть ли причина, по которой я не должен этого делать?

Пример:

#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 (...) {
    }
  }
};

Изменить: добавить пример использования

Ответы [ 2 ]

4 голосов
/ 02 августа 2020

Одна из причин, по которой вы не должны использовать обходной путь, заключается в том, что он не имеет смысла. Применить try-catch к конструктору отверстий подойдет.

struct C {
    A a;
    C() try {
    } catch (...) {
    }
};
1 голос
/ 02 августа 2020

std::optional кажется отличным способом сделать это, если вы используете 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
    }
}

вывод:

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

Вариант, который не требует размера std::optional, - это иметь нераспределенный буфер, но (для безопасности типов) обращаться к нему через ссылку.

#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
...