Как вернуть код ошибки из конструктора?

Я пытаюсь вернуть код ошибки из конструктора, так как конструктор не возвращает код ошибки, я попытался поместить исключение в конструктор. Затем в блоке catch я возвращаю соответствующий код ошибки. Это правильный способ возврата кода ошибки из конструктора?

#include <exception>
#include <iostream>

class A {
 public:
  A() { throw std::runtime_error("failed to construct"); }
};

int main() {
  try {
    A a;
  } catch (const std::exception& e) {
    std::cout << "returining error 1 \n";
    return 1;
  }

  return 0;
}

5 ответов

Согласно isocpp.org, правильный способ обработки ошибки в конструкторе C++ заключается в следующем:

Брось исключение.

Невозможно использовать код ошибки, поскольку конструкторы не имеют возвращаемых типов. Но:

Если у вас нет возможности использовать исключения, "наименее плохой" обходной путь - перевести объект в состояние "зомби", установив внутренний бит состояния, чтобы объект работал как мертвый, даже если он технически еще жив.

Но вы должны действительно использовать исключения, чтобы сигнализировать о сбое в конструкторах, если можете, как сказано:

На практике "зомби" становится довольно уродливым. Конечно, вы должны отдавать предпочтение исключениям над объектами-зомби, но если у вас нет возможности использовать исключения, объекты-зомби могут быть "наименее плохой" альтернативой.

Добавив слой абстракции поверх вашего конструктора, вы можете достичь своей цели, чтобы проверить, не произошел ли конструктор, и вернуть код ошибки. Используя шаблон Initialize-Shutdown, вы можете быть уверены, что если в вашем конструкторе произойдет ошибка, у вас все равно будет доступ к вашему объекту для очистки всего, что вы выделили. Вот пример:

// This model we can potentially throw an exception from the constructor
class Keyboard {
private:
    Device* mDevice;
public:
    Keyboard() { 
        mDevice = ConnectToDevice();
        mDevice.Init(); // Say this can throw exception } // If exception thrown, destructor not called and memory is leaked
                                                          // The object no longer exists, no way to clean up memory

    ~Keyboard() { DisconnectFromDevice(mDevice); } // DisconnectFromDevice() cleans up some dynamically allocated memory
};

// This model we use a Initialize-Shutdown pattern
class Keyboard {
private:
    Device* mDevice;
    bool IsInitialized = false;
public:
    Keyboard() {} 
    ~Keyboard() { DisconnectFromDevice(mDevice); } // DisconnectFromDevice() cleans up some dynamically allocated memory
    void Initialize() {
        mDevice = ConnectToDevice();

        if (this.mDevice == nullptr)
            status = -1;

        mDevice.Init(); // Caller still needs to catch exception if it throws
                        // If exception is thrown here, the caller is responsible for cleaning up
                        // However, the object is still alive so caller can manually call or other cleaning method

        IsInitialized = true;

        return;
    }
    void Shutdown() {
        if (IsInitialized)
            DisconnectFromDevice(mDevice);

        return;
    }
};

Нет способа сделать это. Лучший способ, вероятно, будет иметь статический init() метод, который возвращает экземпляр класса и делает конструктор закрытым. Вы могли бы сделать большую часть строительства из init метод, и просто вернуть код ошибки из этого.

Зависит от того, насколько вероятна ваша ошибка и насколько важна правильная инициализация для дальнейшего выполнения программы.

  • Если сбой при инициализации объекта считается исключительным случаем (например, из-за того, что у вас закончилась память), выведите исключение.
  • Если это ожидаемый сбой, с которым вы хотите разобраться прямо здесь и сейчас, установите объект в состояние сбоя / по умолчанию и разрешите пользователю запрашивать код ошибки (например, когда std::fstream не может открыть файл). Это особенно полезно, если вы хотите повторить инициализацию объекта с другими значениями параметров.

Кстати: если вы решите использовать исключение, я бы, вероятно, не превратил его в код ошибки, если вы абсолютно не обязаны это делать. Исключения там, где они специально разработаны, чтобы вам не приходилось передавать коды ошибок вручную.

Ничто не мешает вам вернуть код ошибки из конструктора, создав исключение.

Вам просто нужно получить свой собственный класс исключений из std::runtime_error:

      #include <stdexcept> // for std::runtime_error
#include <windows.h> // for HRESULT

class com_error : public std::runtime_error
{
    HRESULT hr;
public:
    com_error(const char *_Message, const HRESULT _hr)
    : std::runtime_error(_Message)
    , hr(_hr)
    {
    }

    HRESULT error_code() const noexcept
    {
        return hr;
    }
};

Теперь вы можете создать исключение, содержащее код ошибки:

      class A
{
public:
    A()
    {
        HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
        if (SUCCEEDED(hr)) {
            // TODO: continue initialization
        } else {
            throw com_error("COM initialization failed", hr);
        }
    }
};

И вы можете поймать его и изучить код ошибки:

      try {
    A *a = new A();
    // ...
} catch (com_error& e) {
    HRESULT hr = e.error_code();
    // TODO: Do what you want with error code here
}

Это не значит, что это хорошая идея. Паттерн Initialize/Shutdown лучше использовать, когда конструкция объекта сложная и может потребовать очистки/отката.

Другие вопросы по тегам