【问题标题】:How to return error code from constructor?如何从构造函数返回错误代码?
【发布时间】:2018-01-20 11:34:49
【问题描述】:

我试图从构造函数返回错误代码,因为构造函数 不返回错误代码,我试图在 构造函数。然后在 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;
}

【问题讨论】:

  • 对我来说,让构造函数 noexcept 是一个好习惯……也许,一个好方法可以给对象设置一个状态,比如:无效。我也在寻找更好的答案
  • 只使用例外。错误返回值有各种各样的问题,尤其是它们太容易被忽略。
  • 是的,构造函数应该构造一个有效的对象或者抛出一个异常。
  • @Antoine 这是一个可怕的想法,并且一再被证明有很多很多问题,只需使用例外。
  • @NeilButterworth 是的,感谢您提供了一个愚蠢的编码标准示例。

标签: c++ exception constructor return-code


【解决方案1】:

没有什么可以阻止您通过抛出异常从构造函数返回错误代码。

你只需要从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
}

这并不意味着这是一个好主意。当对象构造复杂并且可能需要清理/回滚时,最好使用初始化/关闭模式。

【讨论】:

    【解决方案2】:

    通过在构造函数上添加一个抽象层,您可以实现检查构造函数是否失败并返回错误代码的目标。通过使用 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, the destructor is 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
    };
    
    // In 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;
        }
    };
    

    【讨论】:

    • 这是嵌入式编程中人们通常不启用 c++ 异常处理的问题的惯用解决方案。不过,通常 Initialize() 会返回一个类似“bool”的状态值来表示成功。
    【解决方案3】:

    取决于您认为此类错误的可能性以及正确初始化对于进一步程序执行的重要性。

    • 如果初始化对象失败被视为异常情况(例如,因为内存不足),则抛出异常。
    • 如果这是您想立即处理的预期失败,请将对象设置为失败/默认构造状态并允许用户查询错误代码(例如,当std::fstream 不是能够打开文件)。如果您想使用不同的参数值重试对象初始化,这将特别有用。

    顺便说一句:如果您决定使用异常,除非您绝对必须,否则我可能不会将其转换为错误代码。专门设计的异常,这样您就不必手动传递错误代码。

    【讨论】:

    • 你不能重试对象初始化,至少不能在同一个对象上。
    • 案例 2 的缺点是现在您必须测试“您是否已正确初始化?”每次使用该对象之前。真是个无赖。
    • @NeilButterworth:也许不是标准意义上的,但我说的是初始化到一个有用的状态(例如,如果你考虑农场,你可以先尝试用用户提供的文件名和如果失败,您可能希望回退到使用 open 访问的默认文件)
    • 初始化在 C++ 中有特定的含义。重复使用这些含义并不是一个好主意,因此您可能需要重新措辞。
    • @user45:是的,但有时这样做更自然。
    【解决方案4】:

    根据isocpp.org,在 C++ 中处理构造函数失败的正确方法是:

    抛出异常。

    由于构造函数没有返回类型,因此无法使用错误代码。 但是:

    如果您没有使用异常的选项,“最坏”的解决方法是通过设置内部状态位将对象置于“僵尸”状态,这样对象的行为就好像它已经死了一样虽然它在技术上仍然存在。

    但是如果可以的话,你真的应该在构造函数中使用异常来表示失败,如前所述:

    在实践中,“僵尸”变得非常丑陋。当然你应该更喜欢异常而不是僵尸对象,但是如果你没有使用异常的选项,僵尸对象可能是“最不坏”的选择。

    【讨论】:

      【解决方案5】:

      没有办法做到这一点。最好的方法可能是拥有一个静态的init() 方法,该方法将返回该类的一个实例并使构造函数私有。您可以通过 init 方法完成大部分构造,并从中返回错误代码。

      【讨论】:

      • 不幸的是,init() 方法不适用于许多类,尤其是那些具有带有带参数的构造函数的成员变量的类。
      • 不是说我同意这个答案,但为了记录,init 函数可以采用完美转发的可变参数模板参数
      • @lapinozz 一个带有任何参数的 init() 函数必须在构造函数的主体中调用,因此不能初始化任何东西——它只能赋值。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-19
      • 1970-01-01
      相关资源
      最近更新 更多