构造函数中的网络连接设置:好还是坏?

时间:2010-01-26 23:24:50

标签: c++ network-programming

我正在处理一个处理与远程进程交互的类,该进程可能有也可能不可用;实际上在大多数情况下它不会。如果不是这样,那个阶级的对象在生活中没有任何目的,需要消失。

不那么难看:

  1. 处理构造函数中的连接设置,如果进程不存在则抛出异常。
  2. 在单独的connect()方法中处理连接设置,如果进程不存在,则返回错误代码。
  3. 在选项1)中,调用代码当然必须包装该类的实例化以及在try()块中处理它的所有其他内容。在选项2中,它可以简单地检查connect()的返回值,如果失败则返回(销毁对象),但它不太符合RAII,

    相关地说,如果我选择选项1),最好抛出一个std :: exception类,从中派生自己的异常类,滚动我自己的未完成的异常类,或者只是抛出一个字符串?我想列出一些失败的迹象,这似乎排除了其中的第一个。

    编辑澄清:远程进程在同一台机器上,因此::connect()调用阻止的可能性很小。

9 个答案:

答案 0 :(得分:6)

我认为在构造函数中执行阻塞 connect()是不好的,因为阻塞性质不是构建对象通常所期望的。因此,您的班级用户可能会对此功能感到困惑。

至于异常,我认为从std :: exception派生一个新类通常是最好的(但也是最多的工作)。这允许捕获器使用catch (const myexception &e) {...}语句对该特定类型的异常执行操作,并且还为catch (const std::exception &e) {...}的所有异常执行一项操作。

请参阅相关问题:How much work should be done in a constructor?

答案 1 :(得分: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

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

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

RAII也支持DRY原则(不要重复自己)。但是,对于选项1,您需要确保异常处理是正确的,并且您没有进入竞争条件。如您所知,如果构造函数中抛出异常,则不会调用析构函数进行清理。也可以使用你可能拥有的任何静态函数,因为你需要锁定它们 - 引导你走螺旋路径。

如果你还没有看到this post,那么这是一个很好的阅读。

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

不要从构造函数连接,阻塞的构造函数是意外的和错误的API设计。

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

答案 6 :(得分:0)

根据RAII的思想,这个定义不是很好吗?获得是初始化。

答案 7 :(得分:0)

如果连接失败,如果你的连接对象实际上不起作用,那么如果所有其他方法总是不执行任何操作或抛出异常,那么使对象存在是没有意义的。出于这个原因,我会在构造函数中执行connect,如果此方法失败,则抛出异常(从std::exception派生)会失败。

但是,您是正确的,该类的客户端可能需要知道构造函数可能会阻塞或失败。出于这个原因,我可能会选择使构造函数为private,并使用静态工厂方法(命名构造函数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;
}

答案 8 :(得分:0)

我原来的帖子中另一个不清楚的地方是,客户端代码连接后与该对象没有任何交互。客户端在其自己的线程中运行,一旦对象被实例化并连接,客户端就会调用一个在父进程持续运行的方法。一旦该过程结束(无论出于何种原因),对象将断开连接并退出客户端线程。如果远程进程不可用,则线程立即退出。因此,在周围放置一个非连接对象并不是一个真正的问题。

我发现另一个原因是不在构造函数中进行连接:这意味着我要么必须处理 de 结构中的拆解,要么单独进行disconnect()调用而不单独connect()电话,闻起来很有趣。拆解是非常重要的,可能会阻塞或抛出,因此在析构函数中执行此操作可能不太理想。