2010-01-26 26 views
9

我正在处理与可能或不可用的远程进程交互的类;事实上在大多数情况下它不会。如果不是这样,那个阶级的一个对象就没有生活的意义,需要消失。构造函数中的网络连接设置:好还是不好?

是不那么难看:在构造函数中

  1. 手柄连接设置,抛出一个异常,如果这个过程是不存在的。
  2. 在单独的connect()方法中处理连接设置,如果进程不存在,则返回错误代码。

在选项1)中,调用代码当然必须包装它的实例化类以及在try()块中处理它的所有其他实例。在选项2中,它可以简单地检查connect()的返回值,如果失败则返回(销毁该对象),但它不符合RAII标准,相关的,如果我选择1),是不是最好抛出一个std :: exception类,派生自己的异常类,抛出我自己的未派生异常类,或者只是抛出一个字符串?我想列举一些失败的迹象,这似乎排除了其中的第一个。

编辑澄清:远程进程是在同一台机器上,所以调用::connect()呼叫很可能会阻止。

回答

6

我认为在构造函数中阻止阻止connect()是不好的,因为阻塞性质并不是通常构建对象所期望的。所以,你的类的用户可能会被这个功能所困惑。对于异常,我认为从std :: exception派生一个新类通常是最好的(也是最多的工作)。这使得捕手可以通过catch (const myexception &e) {...}声明对该特定类型的例外执行操作,并且还可以对所有例外情况使用catch (const std::exception &e) {...}执行一项操作。

见相关的问题:How much work should be done in a constructor?

+1

当然阻塞构造函数也有例外,例如基于RAII的锁。当然,文档和适当的命名非常重要。 – 2010-01-27 00:19:41

+0

+1通过在单独的连接方法中执行阻止操作,您可以使用'cancel'方法取消该操作。 – 2010-01-27 02:26:22

0

我会去的第二个,因为我相信,构造函数不应该做的不是初始化的私有成员任何其他东西。除此之外,处理故障更容易(如不连接)。根据你准备要做的事情,你可以保持这个对象的存在,并在需要时调用connect方法,最大限度地减少创建另一个对象的需要。

至于例外,你应该创建自己的。这将允许调用者在需要时采取特定的回滚操作。

0

不要从构造函数连接,构造函数的块是意想不到的和糟糕的API设计。

编写连接方法并标记您的类不可复制。如果您依赖已经连接的实例,请将构造函数设置为private,并编写一个静态工厂方法来获取预连接​​的实例。

1

如果连接需要很长时间,则将代码放在另一个方法中更合理。尽管如此,您可以(也应该)使用异常来通知调用方您的connect()方法是否成功,而不是返回错误代码。

创建一个从std::exception派生的新异常类,而不是抛出纯数据,甚至抛出其他STL异常也是更明智的做法。您也可以从更为具体的错误描述(例如,来自std::runtime_error)推导出您的例外类,但这种方法不太常见。

1

我认为选项1是一种更好的方法,但您需要考虑如何期望该类的消费者使用此选项?事实上,他们已经连接好了,可以继续并连接(选项1),或者事实上,他们应该可以选择在Connect()状态良好且准备就绪(选项2)时进行调用?

RAII还支持DRY原则(不要重复自己)。然而,对于选项1,您需要确保您有异常处理功能,并且不会遇到竞争状况。如你所知,如果在构造函数中抛出异常,析构函数将不会被调用来清理。也可以改变任何你可能拥有的静态功能,因为你也需要锁定这些静态功能 - 把你引向螺旋路径。

如果你还没有看过this post它还是很好的阅读。

0

根据RAII的想法,这不是定义好吗?获取是初始化。

3

关于抛出异常,它非常好地创建自己的类。作为一个假设的用户,我更喜欢他们是否从std :: exception派生,或者可能是std :: runtime_error(它允许您将错误字符串传递给ctor)。

用户谁希望能赶上你的派生类型,但共同的成语:

try { 
    operation_that_might_throw(); 
    } catch (std::exception& e) { 
    cerr << "Caught exception: " << e.what() << endl; 
    } 

会为你的新的异常类型的工作,以及任何由C++运行时抛出。这基本上是Rule of Least Surprise

0

如果连接对象在连接失败时无法正常工作,那么如果所有其他方法始终不执行任何操作或抛出异常,则该对象不存在意义。因为这个原因,我会在构造函数中执行连接,如果此方法失败,则抛出异常(从std::exception派生)将失败。

然而,你是对的,类的客户端可能需要知道构造函数可能阻塞或失败。出于这个原因,我可能会选择使构造函数保持私有状态,并使用静态工厂方法(名为constructor idiom),以便客户端必须进行明确的MakeConnection调用。

确定没有连接是否致命或是否可以处理离线模式仍然是客户的责任。在前一种情况下,它可以通过价值拥有连接,并让任何连接失败传播给其客户;在后者中,它可以通过指针来拥有对象,最好是“聪明”的。在后一种情况下,它可能会选择尝试在其构造函数中构建自己的连接,或者可能会将其延迟直到需要。

E.g. (警告:代码全部未经测试)

class Connection 
{ 
    Connection(); // Actually make the connection, may throw 
    // ... 

public: 
    static Connection MakeConnection() { return Connection(); } 

    // ... 
}; 

这是需要工作连接的类。

class MustHaveConnection 
{ 
public: 
    // You can't create a MustHaveConnection if `MakeConnection` fails 
    MustHaveConnection() 
     : _connection(Connection::MakeConnection()) 
    { 
    } 

private: 
    Connection _connection; 
}; 

这是一个没有一个可以工作的类。

class OptionalConnection 
{ 
public: 
    // You can create a OptionalConnectionif `MakeConnection` fails 
    // 'offline' mode can be determined by whether _connection is NULL 
    OptionalConnection() 
    { 
     try 
     { 
      _connection.reset(new Connection(Connection::MakeConnection())); 
     } 
     catch (const std::exception&) 
     { 
      // Failure *is* an option, it would be better to capture a more 
      // specific exception if possible. 
     } 
    } 

    OptionalConnection(const OptionalConnection&); 
    OptionalConnection& operator=(const OptionalConnection&); 

private: 
    std::auto_ptr<Connection> _connection; 
} 

最后是一个按需创建一个,并向调用者传播异常。

class OnDemandConnection 
{ 
public: 
    OnDemandConnection() 
    { 
    } 

    OnDemandConnection(const OnDemandConnection&); 
    OnDemandConnection& operator=(const OnDemandConnection&); 

    // Propgates exceptions to caller 
    void UseConnection() 
    { 
     if (_connection.get() == NULL) 
      _connection.reset(new Connection(Connection::MakeConnection())); 

     // do something with _connection 
    } 

private: 
    std::auto_ptr<Connection> _connection; 
} 
0

那是在我原来的职位还不清楚另一件事是,客户端代码没有与此对象的任何交互,一旦它的连接。客户端运行在自己的线程中,一旦对象被实例化并连接起来,客户端就会调用一个在父进程期间运行的方法。一旦该过程结束(无论出于何种原因),该对象将断开连接并退出客户端线程。如果远程进程不可用,则线程立即退出。所以有一个非连接的对象躺在地上并不是一个真正的问题。

我发现了另一个理由不这样做在构造函数中的连接:这意味着我要么以处理 structor拆卸,或有一个单独的disconnect()电话没有单独connect()调用,它闻起来好笑。拆解是不平凡的,可能会阻止或抛出,因此在析构函数中执行它可能不太理想。

相关问题