如何优雅地处理异常抛出构造函数

How to elegantly handle exception-throwing constructors?

本文关键字:构造函数 异常 处理 何优雅      更新时间:2023-10-16

我有时想知道如何处理可以放入构造函数的对象的构造。我想知道你是怎么做的。

请考虑以下片段。我有一个名为TCPMessage的类,它表示我的"服务器"通过TCP接收的消息。如果接收到的消息无效(即TCPMessage的构造函数中计算出的CRC32未检出(,则TCPMessage的构造函数将抛出。

下面是我的做法。你知道有什么更好的方法吗?我在问,因为它看起来并不太优雅。

void TCPConnection::handleRead(
  const boost::system::error_code& error,
  char* read_buffer
)
{
  if (!error) {
    TCPMessage* message = NULL; // being verbose here
    try {
      message = new TCPMessage(read_buffer);
    } catch (const char* e) {
      std::cerr << "Instatiating TCPMessage: " << e << std::endl;
    } catch (...) {
      std::cerr << "Instatiating TCPMessage: unknown exception." << std::endl;
    }
    if (message) {
      // if created succesfully
      // process the message and delete it
      SeekurJrRC::Core::Driver& driver = boost::asio::use_service<SeekurJrRC::Core::Driver>(_io_service);
      driver.processMessage(*message);
      delete message;
    }
  }
  delete [] read_buffer;
}

哦,是的,我知道不要在另一个函数中使用char* read_buffer并删除它。shared_ptr,我知道。

您的问题不是例外,而是原始指针和缺乏RAII。

对代码进行一点消毒:

void TCPConnection::handleRead(
  const boost::system::error_code& error,
  char* read_buffer
)
{
  if (!error) {
    try {
      TCPMessage message(read_buffer);
      SeekurJrRC::Core::Driver& driver = boost::asio::use_service<SeekurJrRC::Core::Driver>(_io_service);
      driver.processMessage(message);
    } catch (const char* e) {
      std::cerr << "Instatiating TCPMessage: " << e << std::endl;
    } catch (...) {
      std::cerr << "Instatiating TCPMessage: unknown exception." << std::endl;
    }
  }
}

new调用应该被封装在RAII对象中,而不是在用户代码中随意摆动。delete调用不应该显式,而应该由RAII对象中定义的析构函数来处理。

然后,如果抛出异常,您的对象将自动被销毁并自行清理,您不需要过于复杂的"尝试/捕捉/检查成功"舞蹈。如果没有引发异常,则正常继续。如果抛出了try块,则会自动销毁对象。

请注意,在这里您实际上不再需要try/catch块。您使用catch的唯一目的是打印一条错误消息。对于程序流或防止资源泄漏来说,这是不必要的。通常情况下,您会处理错误,这样做是有意义的。大概这在调用树的更高位置,在那里你知道如何处理失败的读取。在这个级别上,让异常转义以指示发生了错误更有意义。

void TCPConnection::handleRead(
  const boost::system::error_code& error,
  char* read_buffer
)
{
  if (!error) {
    TCPMessage message(read_buffer);
    SeekurJrRC::Core::Driver& driver = boost::asio::use_service<SeekurJrRC::Core::Driver>(_io_service);
    driver.processMessage(message);
  }
}

您必须在某个地方处理异常。如果您不想每次执行new TCPMessage(read_buffer)时都将代码弄乱,那么您可以为构造函数使用特殊语法,只在一个位置处理异常。例如

class TCPMessage {
...
public:
  TCPMessage (const char *read_buffer)
  try {
    ...
  }
  catch(const char *p){ ... }
  catch(...) { ... }
...
};

将处理封装在try-catch块中有什么问题?

void TCPConnection::handleRead(
  const boost::system::error_code& error,
  char* read_buffer
)
{
  if (!error) {
     try {
         std::auto_ptr<TCPMessage> message(new TCPMessage(read_buffer));
         // if created succesfully
         // process the message and delete it
         SeekurJrRC::Core::Driver& driver = boost::asio::use_service<SeekurJrRC::Core::Driver>(_io_service);
         driver.processMessage(*message);
     } catch (const char* e) {
         std::cerr << "Instatiating TCPMessage: " << e << std::endl;
     } catch (...) {
         std::cerr << "Instatiating TCPMessage: unknown exception." << std::endl;
     }
  }
  delete [] read_buffer;
}

当然,当代码的另一部分可以抛出时,您应该使用合适的智能指针来确保异常安全。最好不要在这个函数中正确处理所有异常。

通常的方法是在可以解决异常的地方处理异常。

例如,如果抛出"connection was droped"异常,您可以在某个地方处理它,询问用户该怎么办:重新连接,或者关闭应用程序。

您不一定需要在该函数中处理异常。我看到你只是在打印错误信息。如果这是唯一可以做的事情,那么没关系。正如你所说,最好使用智能指针。类似这样的东西:

void TCPConnection::handleRead(
  const boost::system::error_code& error,
  char* read_buffer
)
{
  if (!error) {
    try{
    std::auto_ptr< TCPMessage > message( new TCPMessage(read_buffer) );
    SeekurJrRC::Core::Driver& driver = boost::asio::use_service<SeekurJrRC::Core::Driver(_io_service);
    driver.processMessage(message.release());
    } catch (const SomeException e) {
      std::cerr << "Instatiating TCPMessage: " << e << std::endl;
    } catch (...) {
      std::cerr << "Instatiating TCPMessage: unknown exception." << std::endl;
    }
  }
  delete [] read_buffer;
}

请注意,最好捕获一些自定义异常类型,而不是const char*

构造函数应该只处理与资源分配相关的作业。如果在使用之前需要进行复杂的初始化,我们需要一个专用的init成员函数。ATL就是这么做的。这样我们就可以避免在构造函数中抛出异常。

智能指针不能解决问题,当构造函数中抛出异常时,我们可能会出现内存泄漏。

    std::autor_ptr<Widget> pWidget(new Widget()); 

上述声明至少可以做三件事:

   1. call new operator to allocate memory
   2. construnct an object by calling its constructor 
   3. constuct the smart poniter. 

如果在第二步中抛出异常,则不会构造智能指针,也不会对其进行销毁。因此,我们泄漏了在第一步中分配的内存。