如何处理在C + +构造函数中的失败?
我想在一个类的构造函数中打开一个文件。 开放可能会失败,那么对象build设就无法完成。 如何处理这个失败? 抛出exception? 如果这是可能的,如何处理它在一个非抛出的构造函数?
如果对象构造失败,则抛出exception。
另一种方法很糟糕。 如果施工成功,你将不得不创build一个标志,并在每种方法中检查。
我想在一个类的构造函数中打开一个文件。 开放可能会失败,那么对象build设就无法完成。 如何处理这个失败? 抛出exception?
是。
如果这是可能的,如何处理它在一个非抛出的构造函数?
您的select是:
- 重新devise应用程序,所以它不需要构造函数是非抛出 – 真的,如果可能的话
- 添加一个标志,并testing成功的build设
- 你可以让每个成员函数在构造函数testing标志后立即调用,理想情况下,如果它被设置,则返回一个错误代码
- 这是丑陋的,很难保持正确的,如果你有一个不稳定的开发团队工作的代码。
- 您可以通过使对象多态地推迟到两种实现之一来获得一些编译时检查:成功构build的和始终错误的版本,但会引入堆使用和性能成本。
- 通过logging在使用对象之前调用某个“is_valid()”或类似函数的需求,您可以将检查标志从被调用代码检查到被调用者的负担:再次出错和难看,但是更加分散,失控。
- 如果你支持这样的事情,你可以使这个调用方法更容易一些,也更加本地化:
if (X x) ...
(例如,对象可以在布尔上下文中计算,通常通过提供operator bool() const
或类似的整数转换),但是您没有在范围内查询错误的详细信息。 这可能是熟悉的,例如,if (std::ifstream f(filename)) { ... } else ...;
- 如果你支持这样的事情,你可以使这个调用方法更容易一些,也更加本地化:
- 你可以让每个成员函数在构造函数testing标志后立即调用,理想情况下,如果它被设置,则返回一个错误代码
- 让调用者提供一个他们负责打开的stream……(称为dependency injection或DI)…在某些情况下,这种方式不能很好地工作:
- 当你在构造函数中使用stream时,你仍然可能有错误,那么呢?
- 文件本身可能是一个实现细节,应该是你的类的私有而不是暴露给调用者:如果你想稍后移除这个需求呢? 例如:您可能一直在阅读文件中预先计算结果的查询表,但是计算速度如此之快,因此无需进行预先计算 – 这在企业环境中的每一个时间点都会删除文件是痛苦的(有时甚至在企业环境中不切实际)客户端使用,并强制更多的重新编译,而不是简单地重新链接。
- 强制调用者为构造函数设置的成功/失败/错误条件variables提供缓冲区:例如,
bool worked; X x(&worked); if (worked) ...
bool worked; X x(&worked); if (worked) ...
- 这种负担和冗长引起了人们的注意,并希望使得调用者在构build对象之后更多地意识到需要咨询variables
- 强制调用者通过可以使用返回码和/或exception的其他函数来构造对象:
-
if (X* p = x_factory()) ...
- Smart_Ptr_Throws_On_Null_Deref p_x = x_factory();
</li> <li>
X x; //不可用; 如果(init_x(&x))…` - 等等…
-
简而言之,C ++旨在为这些问题提供优雅的解决scheme:在这种情况下是exception。 如果人为地限制自己使用它们,那么不要指望有别的事情做一半的工作。
(PS我喜欢传递将被指针修改的variables – 根据以上的worked
,我知道常见问题解答不鼓励它,但不同意这个推理。除非你有一些常见问题,否则对讨论没什么兴趣。
我对这个具体情况的build议是,如果你不想让一个构造函数失败,因为如果不能打开一个文件,那就避免这种情况。 如果这是你想要的,将已经打开的文件传递给构造函数,那么它不会失败…
我想在一个类的构造函数中打开一个文件。
几乎肯定是一个坏主意。 在施工期间打开文件的情况非常less。
开放可能会失败,那么对象build设就无法完成。 如何处理这个失败? 抛出exception?
是的,就是这样。
如果这是可能的,如何处理它在一个非抛出的构造函数?
使你的类的完全构造的对象可能是无效的。 这意味着提供validation例程,使用它们等等
一种方法是抛出exception。 另一个是有一个'bool is_open()'或'bool is_valid()'functuon,如果在构造函数中出错了,返回false。
这里有一些评论说在构造函数中打开文件是错误的。 我将指出ifstream是C ++标准的一部分,它具有以下构造函数:
explicit ifstream ( const char * filename, ios_base::openmode mode = ios_base::in );
它不会抛出exception,但它有一个is_open函数:
bool is_open ( );
构造函数可能会打开一个文件(不一定是个坏主意),如果文件打开失败,或者input文件不包含兼容数据,则可能会抛出该文件。
抛出exception是一个构造函数的合理行为,然而你将会限制它的使用。
-
您将无法创build在“main()”之前构造的此类的静态(编译单元文件级别)实例,因为构造函数只应该在常规stream程中抛出。
-
这可以延伸到稍后的“第一次”懒惰评估,第一次加载的东西,例如在boost :: once构造中,call_once函数不应该抛出。
-
您可以在IOC(反转控制/dependency injection)环境中使用它。 这就是IOC环境有利的原因。
-
确定如果你的构造函数抛出那么你的析构函数将不会被调用。 因此,在此之前,在构造函数中初始化的任何内容都必须包含在RAII对象中。
-
如果这样刷新写入缓冲区,那么更危险的方式就是closures析构函数中的文件。 完全没有办法妥善处理此时可能出现的任何错误。
通过将对象置于“失败”状态,您可以毫无例外地处理它。 在不允许投掷的情况下,您必须这样做,但是当然您的代码必须检查错误。
新的C ++标准在很多方面对此进行了重新定义,现在是重新审视这个问题的时候了。
最好的select:
-
命名可选 :有一个最小的私有构造函数和一个命名的构造函数:
static std::experimental::optional<T> construct(...)
。 后者试图build立成员字段,确保不变,只有调用私有构造函数才能成功。 私有构造函数只填充成员字段。 testing可选项很容易,而且价格便宜(甚至可以在很好的实施中保留副本)。 -
function风格 :好消息是,(非命名)构造函数永远不是虚拟的。 因此,可以用一个静态模板成员函数replace它们,除了构造函数参数外,它还需要两个(或更多)lambdaexpression式:一个是成功的,一个是失败的。 “真正的”构造函数仍然是私有的,不能失败。 这可能听起来太过于夸张了,但lambdas已经被编译器优化了。 你甚至可以用这种方式免除这个选项。
好的select:
-
exception :如果一切都失败,请使用exception – 但请注意,在静态初始化过程中不能捕获exception。 在这种情况下,可能的解决方法是让函数的返回值初始化对象。
-
生成器类 :如果构造复杂,有一个类进行validation并可能进行一些预处理,以至于操作不会失败。 让它有一个方法来返回状态(是,错误函数)。 我亲自把它堆叠起来,所以人们不会把它传递出去。 然后让它有一个构build其他类的
.build()
方法。 如果build设者是朋友,build设者可以是私人的。 它甚至可能只需要构build器可以构build的东西,以便logging该构造器只能由构build器调用。
不好的select:(但看过很多次)
-
标志 :不要因为有一个'无效的'状态而把你的类变成不变的。 这正是我们
optional<>
。 考虑optional<T>
可能是无效的,T
不能。 仅在有效对象上工作的(成员或全局)函数适用于T
一个肯定会返回T
有效作品。 一个可能返回无效的对象返回optional<T>
。 一个可能会使对象失效的非常量optional<T>&
或optional<T>*
。 这样,你不需要检查每个函数,你的对象是有效的(if
s可能会变得有点贵),但是也不会在构造函数中失败。 -
默认构造和设置器 :这与Flag基本相同,只是这次你不得不有一个可变的模式。 忘记setter,他们不必要地复杂化你的类不变。 记住保持你的课程简单,而不是简单的build设。
-
使用ctor参数的默认构造函数和
init()
:这不过是一个返回optional<>
的函数,但是需要两个构造函数来修改你的不variables。 -
采取
bool& succeed
:这是我们在optional<>
之前所做的。optional<>
的原因是优越的,你不能错误的(或不小心!)忽略succeed
标志,并继续使用部分构造的对象。 -
返回一个指针的工厂 :这是不太一般的,因为它强制dynamic分配对象。 要么返回给定types的托pipe指针(因此限制分配/作用域架构),要么返回裸露的ptr,要么冒险客户端泄漏。 另外,通过性能方面的移动原理图,这可能会变得不太理想(当堆栈中的本地机器人非常快速且caching友好的时候)。
例:
#include <iostream> #include <experimental/optional> #include <cmath> class C { public: friend std::ostream& operator<<(std::ostream& os, const C& c) { return os << c.m_d << " " << c.m_sqrtd; } static std::experimental::optional<C> construct(const double d) { if (d>=0) return C(d, sqrt(d)); return std::experimental::nullopt; } template<typename Success, typename Failed> static auto if_construct(const double d, Success success, Failed failed = []{}) { return d>=0? success( C(d, sqrt(d)) ): failed(); } /*C(const double d) : m_d(d), m_sqrtd(d>=0? sqrt(d): throw std::logic_error("C: Negative d")) { }*/ private: C(const double d, const double sqrtd) : m_d(d), m_sqrtd(sqrtd) { } double m_d; double m_sqrtd; }; int main() { const double d = 2.0; // -1.0 // method 1. Named optional if (auto&& COpt = C::construct(d)) { C& c = *COpt; std::cout << c << std::endl; } else { std::cout << "Error in 1." << std::endl; } // method 2. Functional style C::if_construct(d, [&](C c) { std::cout << c << std::endl; }, [] { std::cout << "Error in 2." << std::endl; }); }