我正在处理一个处理与远程进程交互的类,该进程可能有也可能不可用;实际上在大多数情况下它不会。如果不是这样,那个阶级的对象在生活中没有任何目的,需要消失。
不那么难看:
connect()
方法中处理连接设置,如果进程不存在,则返回错误代码。在选项1)中,调用代码当然必须包装该类的实例化以及在try()
块中处理它的所有其他内容。在选项2中,它可以简单地检查connect()的返回值,如果失败则返回(销毁对象),但它不太符合RAII,
相关地说,如果我选择选项1),最好抛出一个std :: exception类,从中派生自己的异常类,滚动我自己的未完成的异常类,或者只是抛出一个字符串?我想列出一些失败的迹象,这似乎排除了其中的第一个。
编辑澄清:远程进程在同一台机器上,因此::connect()
调用阻止的可能性很小。
答案 0 :(得分:6)
我认为在构造函数中执行阻塞 connect()
是不好的,因为阻塞性质不是构建对象通常所期望的。因此,您的班级用户可能会对此功能感到困惑。
至于异常,我认为从std :: exception派生一个新类通常是最好的(但也是最多的工作)。这允许捕获器使用catch (const myexception &e) {...}
语句对该特定类型的异常执行操作,并且还为catch (const std::exception &e) {...}
的所有异常执行一项操作。
答案 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()
电话,闻起来很有趣。拆解是非常重要的,可能会阻塞或抛出,因此在析构函数中执行此操作可能不太理想。