在 C++ 的构造函数中抛出异常是一个好习惯吗?班级?

发布于 2024-10-10 05:03:13 字数 1177 浏览 1 评论 0原文

我有这个构造函数,它抛出异常

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

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

I've got this constructor which throws an exception

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() does a memset of _hints and sets some variables.

I test it with the google test framework like this:

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

The test fails with a core dump:

[ 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

Besides the fact that I dont know exactly what goes wrong, I suspect some uninitialised object gets deleted(?), a lot seems to happen under the hood, so I started to wonder if it is good practice to throw an exception in a constructor. Is it maybe better to put this functionality in another function which I can call after the creation of the object, and handle the exception afterwards?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

萌能量女王 2024-10-17 05:03:13

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

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

IMHO, throwing an exception in a constructor is the best way to handle a situation like this - do you really want a usable object if there is no socket? it doesn't make sense to me. If it's failing to resolve that address, there's a reason for that and that is worthy of an exception (as long as you handle it correctly!)

In your particular case, you should test the return value and make the exception more useful... (for example, HostNotFound - which I'd guess is the case here)

浅浅淡淡 2024-10-17 05:03:13

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

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

Yes it is. You actually have no choice: constructors have no return values.

But take care of exception safety. See http://www.drdobbs.com/184403429 for instance, or google "strong exception guarantee". Indeed, an object whose constructor has thrown will not be destructed (it has never existed) and must be left in a state which doesn't leak resources.

幸福%小乖 2024-10-17 05:03:13

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

Of course, the only reasonable thing to do when you can't construct an object is to throw exception, otherwise you would end up with some zombie objects. And answering your other question, no, you can't destroy an object that wasn't created.

娇妻 2024-10-17 05:03:13

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

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

Yes, throwing an exception is the most direct way to report that the constructor encountered problems, since they do not return values.

Destructors, on the other hand, should not throw exceptions, since they will be called in the stack unwinding phase of exception handling, and throwing another exception at this point would result in an abort.

相思故 2024-10-17 05:03:13

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

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

2) 抛出异常来自构造函数

There are already at least two discussions about this on SO. On both throwing exceptions from constructors and two-phase initialization:

1) When is it right for a constructor to throw an exception?

2) Throwing exceptions from constructors

煞人兵器 2024-10-17 05:03:13

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

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

If you learn at construction that you won't be able to create a valid object then throwing an exception is arguably your best option. The user of the class is thus forbidden from carrying on in case of error, unless they can handle it. That's usually the correct behaviour of a program.

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

If you can't fulfil step one then the other steps are futile. That's what we use RAII for: we acquire all the resources we need first, somewhat analogous to the old C style of programming with respect to stack variables. With RAII if any of the resources fail to acquire -- via an exception in the constructor -- then all previously acquired resources will release automatically and the second step will never be attempted. All done behind the scenes, automatically, but this assumes you throw an exception in the constructor if you can't create the object. It also assumes the destructor will do the clean-up.

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.
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文