4

我有这个引发异常的构造函数

GenericSocket::GenericSocket(const string& hostname, 
                             const string& servname):
                             _hostname(hostname),
                             _servname(servname)
{  
    initHints();
    int rv;
    if((rv = getaddrinfo(_hostname.c_str(), 
                    _servname.c_str(), 
                    &_hints, 
                    &_servinfo)) != 0) {  
        throw GenericSocketException();
    }  

} 

initHints() 执行 _hints 的 memset 并设置一些变量。

我用谷歌测试框架测试它,如下所示:

TEST(CreateObject2, getaddrinfoException)
{
    mgs_addrinfo_return = 1; 
    ASSERT_THROW(new GenericSocket("testhost", "4242"), GenericSocketException);
}

测试失败并出现核心转储:

[ RUN      ] CreateObject2.getaddrinfoException
socket creation failed
terminate called after throwing an instance of 'common::GenericSocketException'
  what():  Socket creation failed
[1]    43360 abort (core dumped)  ./bin/test_common

除了我不知道到底出了什么问题之外,我怀疑一些未初始化的对象被删除了(?),很多事情似乎在幕后发生,所以我开始怀疑在构造函数中抛出异常是否是一种好习惯。将这个功能放在另一个我可以在创建对象后调用的函数中,然后处理异常是否更好?

4

6 回答 6

11

恕我直言,在构造函数中抛出异常是处理这种情况的最佳方法——如果没有套接字,你真的想要一个可用的对象吗?这对我来说没有意义。如果它无法解析该地址,那是有原因的,并且值得例外(只要您正确处理它!)

在您的特定情况下,您应该测试返回值并使异常更有用......(例如,HostNotFound- 我猜这里就是这种情况)

于 2011-01-04T20:06:06.963 回答
6

是的。你实际上别无选择:构造函数没有返回值。

但要注意异常安全。例如,参见http://www.drdobbs.com/184403429 ,或谷歌“强例外保证”。实际上,构造函数已抛出的对象不会被破坏(它从未存在过)并且必须处于不泄漏资源的状态。

于 2011-01-04T20:08:56.207 回答
5

当然,当你无法构造一个对象时,唯一合理的做法就是抛出异常,否则你最终会得到一些僵尸对象。并回答您的另一个问题,不,您不能销毁未创建的对象。

于 2011-01-04T20:08:41.317 回答
4

是的,抛出异常是报告构造函数遇到问题的最直接方法,因为它们不返回值。

另一方面,析构函数应该抛出异常,因为它们将在异常处理的堆栈展开阶段被调用,并且此时抛出另一个异常将导致中止。

于 2011-01-04T20:48:13.183 回答
1

在 SO 上已经至少有两次关于此的讨论。在从构造函数和两阶段初始化中抛出异常:

1)构造函数什么时候抛出异常合适?

2)从构造函数中抛出异常

于 2011-01-04T21:13:39.927 回答
1

如果您在构造时了解到您将无法创建有效对象,那么抛出异常可以说是您的最佳选择。因此,该类的用户被禁止在出现错误的情况下继续进行,除非他们能够处理它。这通常是程序的正确行为。

function()
{
    // 1. acquire resources.
    // 2. perform action.
    // 3. release resources.
}

如果您无法完成第一步,那么其他步骤都是徒劳的。这就是我们使用 RAII 的目的:我们首先获取我们需要的所有资源,这有点类似于关于堆栈变量的旧 C 编程风格。使用 RAII,如果任何资源无法获取 - 通过构造函数中的异常 - 然后所有先前获取的资源将自动释放,并且永远不会尝试第二步。所有这些都是在幕后自动完成的,但这假设您在无法创建对象时在构造函数中抛出异常。它还假设析构函数将进行清理。

struct Connection {
    Connection() {
        if( ! connect() ) throw ConnectionError();
    }
    ~Connection() {  // may not throw under any circumstances
        try { disconnect(); }
        catch( ... ) { }
    }
    void send(const std::string& message);
    // ...
private:
    bool connect();
    // ...
};

void post(const std::string& message)
{
    // step one is easy for the user:
    Connection c;
    // step two is the bulk of the work:
    c.send("HELO");
    // step three is automatically done for the user.
}
于 2011-01-04T21:15:15.210 回答