【问题标题】:Complex error handling复杂的错误处理
【发布时间】:2010-04-28 15:37:19
【问题描述】:

我有一段特别难看的网络代码。我正在使用 asio 但这对于这个问题并不重要。我认为除了关闭它之外没有其他方法可以解除绑定。问题是open()bind()listen() 都可以抛出system_error。所以我用一个简单的try/catch 处理了代码。写的代码坏了。

using namespace boost::asio;

class Thing
{
public:

   ip::tcp::endpoint m_address;

   ip::tcp::acceptor m_acceptor;

   /// connect should handle all of its exceptions internally.
   bool connect()
   {
      try
      {
         m_acceptor.open( m_address.protocol() );
         m_acceptor.set_option( tcp::acceptor::reuse_address(true) );

         m_acceptor.bind( m_address );
         m_acceptor.listen();

         m_acceptor.async_accept( /*stuff*/ );
      }
      catch( const boost::system::system_error& error )
      {
         assert(acceptor.is_open());
         m_acceptor.close();
         return false;
      }
      return true;
   }

   /// don't call disconnect unless connect previously succeeded.
   void disconnect()
   {
      // other stuff needed to disconnect is ommited
      m_acceptor.close();
   }
};

错误是如果套接字连接失败,它将尝试在 catch 块中关闭它并抛出另一个关于关闭从未打开的接受器的 system_error。

一种解决方案是在 catch 块中添加一个if( acceptor.is_open() ),但这样做的味道不对。有点像将C 风格的错误检查与c++ 异常混合。如果我要去哪条路线,我还不如使用open()的非投掷版本。

boost::system::error_code error;
acceptor.open( address.protocol, error );
if( ! error )
{
    try
    {
       acceptor.set_option( tcp::acceptor::reuse_address(true) );

       acceptor.bind( address );
       acceptor.listen();

       acceptor.async_accept( /*stuff*/ );
    }
    catch( const boost::system::system_error& error )
    {
       assert(acceptor.is_open());
       acceptor.close();
       return false;
    }
}
return !error;

有没有一种优雅的方法可以使用 RAII 和 try/catch 块来处理这些可能的异常?

在使用异常时试图避免if( error condition ) 样式错误处理是不是我的想法是错误的?

【问题讨论】:

    标签: c++ exception exception-handling


    【解决方案1】:

    我建议只对open 进行单独的错误处理,因为之前和之后有不同的清理:

    bool connect()
    {
      try {
         m_acceptor.open( m_address.protocol() );
      } catch( const boost::system::system_error& error ) {
         return false;
      }
    
      try {
         m_acceptor.set_option( tcp::acceptor::reuse_address(true) );
    
         m_acceptor.bind( m_address );
         m_acceptor.listen();
    
         m_acceptor.async_accept( /*stuff*/ );
      } catch( const boost::system::system_error& error ) {
         m_acceptor.close();
         return false;
      }
      return true;
    }
    

    【讨论】:

    • 这包括两个 try-catch 块,这很昂贵并且不符合 RAI C++ 习惯用法。
    【解决方案2】:

    使用 try-catch 您可以考虑到 system_error 有一个 error_code 可以为您提供真正的原因。所以你可以在 catch 语句上测试这个 error_code。

    要使用 RAI,你需要在构造函数上进行连接,在析构函数上断开连接,但我不知道背后是什么

    acceptor.async_accept( /*stuff*/ );
    

    所以也许你需要避开这部分。 东西;

    {
     Connector conn(th); / connect on constructor
     // ... th.async_accept  
     // do some work while connected
    }
     // disconnect on destructor
    

    连接器将使用特定的成员变量 is_open 来处理接收器是否打开,该变量在 acceptor.open() 成功后设置。

       Connector::Connector(...)
       : ...
       , is_open(false)
       {
         m_acceptor.open( m_address.protocol() );
         is_open=true;
         m_acceptor.set_option( tcp::acceptor::reuse_address(true) );
    
         m_acceptor.bind( m_address );
         m_acceptor.listen();
    
         m_acceptor.async_accept( /*stuff*/ );
       }
    
       Connector::~Connector(...)
       {
         // other stuff needed to disconnect is omitted
         if (is_open) m_acceptor.close();
       }
    

    【讨论】:

    • 这实际上是将if(is_open) 移动到~Connector,而不是将其留在connect() 函数中。一个优点是如果我需要在其他地方打开一个接受器,它可以避免代码重复。在我的特殊情况下,我不会。
    • 好吧,代码没有 try-catch 块。你不觉得这是一个优势吗?您询问 RAI 如何简化您的代码,提案会变得更简单吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-07
    • 1970-01-01
    • 1970-01-01
    • 2011-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多