如何从构造函数返回错误代码?

8

我正在尝试从构造函数中返回错误代码,因为构造函数不会返回错误代码,所以我尝试在构造函数中抛出异常。然后在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;
}

3
请使用异常处理。错误返回值存在各种问题,特别是它们很容易被忽视。 - user2100815
3
构造函数应该要么构造一个有效的对象,要么抛出异常。 - user7860670
3
@Antoine 这是一个可怕的想法,并且已经反复证明有许多问题,只需使用异常处理即可。 - user2100815
2
@NeilButterworth 是的,谢谢你提供一个愚蠢的编码标准的例子。 - Deduplicator
1
一旦你发现自己面对着一个没有例外的编码标准,很可能是时候拿出你可靠的 C 语言文本了,因为他们真正想表达的是我们需要 C 代码。 - user4581301
显示剩余6条评论
5个回答

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

抛出异常。

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

如果您不能使用异常,那么“最不好的”解决方法是通过设置内部状态位将对象置于“僵尸”状态,使对象表现得像已经死亡一样,即使它在技术上仍然存活。

但是,如果可以的话,应该真正使用异常来信号化构造函数的失败,如下所述:

实际上,“僵尸对象”会变得非常丑陋。当然,您应该优先考虑使用异常而不是僵尸对象,但是如果您无法使用异常,则僵尸对象可能是“最不好的”选择。


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

不幸的是,对于许多类而言,特别是那些具有带参数构造函数的成员变量的类,init() 方法并不适用。 - user2100815
不是说我同意这个答案,但是为了记录,init函数可以接受完美转发的可变模板参数。 - lapinozz
@lapinozz 在构造函数的主体中必须调用一个带有任何参数的init()函数,因此不能初始化任何内容 - 它只能赋值。 - user2100815

1
通过在构造函数上添加一层抽象,您可以实现检查构造函数是否失败并返回错误代码的目标。 通过使用初始化-关闭模式,您可以确保如果构造函数中出现错误,您仍然可以访问对象以清理任何分配的内容。以下是示例:
// 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;
    }
};

1
这是嵌入式编程中问题的惯用解决方案,人们通常不启用C++异常处理。尽管通常情况下,Initialize() 返回一个状态值,如“bool”来指示成功。 - BitTickler

0

这取决于您认为此类错误的可能性以及适当初始化对进一步程序执行的关键性。

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

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


方案2的缺点是现在你必须在每次使用对象之前测试“是否已正确初始化?”这会变得非常烦人。 - user4581301
@NeilButterworth:也许不是标准意义上的初始化,但我指的是将其初始化为有用的状态(例如,如果你考虑农场,你可以首先尝试使用用户提供的文件名构建它,如果失败了,你可能想要回退到一个默认文件,通过使用open来访问)。 - MikeMB
初始化在C++中有特定的含义。重复使用这样的含义并不是一个好主意,因此您可能需要重新措辞您的答案。 - user2100815
@NeilButterworth:好的,那么你会如何称呼例如int函数的效果? - MikeMB
我猜你的意思是init()。实际上我不知道,因为我从来不会使用这样的东西。也许是“默认重新赋值”? - user2100815
显示剩余4条评论

0

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

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接