我有一个CPP类,其构造函数执行一些操作。其中一些操作可能会失败。我知道构造函数不会返回任何东西。
我的问题是
除了初始化构造函数中的成员之外,是否可以执行其他操作?
是否可以告诉调用函数构造函数中的某些操作已失败?
new ClassName()
如果构造函数中发生某些错误,我可以使返回NULL吗?
Square
,有一个构造函数的一个参数,一个边长,你要检查,如果该值大于0
我有一个CPP类,其构造函数执行一些操作。其中一些操作可能会失败。我知道构造函数不会返回任何东西。
我的问题是
除了初始化构造函数中的成员之外,是否可以执行其他操作?
是否可以告诉调用函数构造函数中的某些操作已失败?
new ClassName()
如果构造函数中发生某些错误,我可以使返回NULL吗?
Square
,有一个构造函数的一个参数,一个边长,你要检查,如果该值大于0
Answers:
是的,尽管某些编码标准可能会禁止它。
是。推荐的方法是引发异常。或者,您可以将错误信息存储在对象内部,并提供访问此信息的方法。
没有。
您可以创建一个静态方法来执行计算,并在成功的情况下返回对象,或者在失败的情况下返回对象。
根据对象的这种构造方式,最好创建另一个允许以非静态方法构造对象的对象。
间接调用构造函数通常称为“工厂”。
这也将允许您返回空对象,这可能是比返回空值更好的解决方案。
NULL
。例如,int foo() { return NULL
实际上您将返回0
(零)一个整数对象。在std::string foo() { return NULL; }
您不小心调用的情况下std::string::string((const char*)NULL)
,这是未定义的行为(NULL并不指向\ 0终止的字符串)。
int
。例如,这std::allocator<int>
是一个完美的理智工厂。
@SebastianRedl已经给出了简单直接的答案,但是一些额外的解释可能会有用。
TL; DR =有一种样式规则可以使构造函数保持简单,这是有原因的,但是这些原因主要与历史性(或根本就是不好的)编码风格有关。构造函数中的异常处理已得到很好的定义,并且仍将为完全构造的局部变量和成员调用析构函数,这意味着惯用的C ++代码应该没有任何问题。样式规则仍然存在,但是通常这不是问题-并非所有初始化都必须在构造函数中,尤其是不一定在构造函数中。
这是一种常见的样式规则,即构造函数应尽其所能来设置已定义的有效状态的绝对最小值。如果您的初始化更为复杂,则应在构造函数之外进行处理。如果构造函数无法设置便宜的初始化值,则应减弱类强制执行的不变式以添加一个。例如,如果为类管理分配存储空间太昂贵,则添加一个尚未分配的null状态,因为当然,像null这样的特殊情况永远不会给任何人造成任何问题。啊
尽管很常见,但以这种极端的形式肯定不是绝对的。尤其是,正如我的讽刺所指出的那样,我在营地中说削弱不变性几乎总是代价太高。但是,样式规则背后有一些原因,并且有办法同时具有最少的构造函数和强不变性。
原因与自动析构函数清除有关,尤其是在遇到异常时。基本上,当编译器负责调用析构函数时,必须有一个明确定义的点。当您仍在进行构造函数调用时,该对象不一定是完全构造的,因此调用该对象的析构函数是无效的。因此,只有在构造函数成功完成后,销毁对象的责任才转移到编译器。这就是RAII(资源分配是初始化),实际上并不是最好的名字。
如果在构造函数内部发生异常抛出,则需要显式清除部分构造的任何东西,通常是在try .. catch
。
然而,组件其已成功构建了对象已经是编译器的责任。这意味着实际上,这并不是什么大问题。例如
classname (args) : base1 (args), member2 (args), member3 (args)
{
}
该构造函数的主体为空。只要是的构造函数base1
,member2
并且member3
它们是异常安全的,就没有什么可担心的。例如,如果member2
throws 的构造函数,则该构造函数负责清理自身。该基础base1
已经完全构建,因此将自动调用其析构函数。member3
甚至从未进行过部分构造,因此不需要清理。
即使存在主体,在抛出异常之前已完全构造的局部变量也将像其他任何函数一样被自动破坏。处理原始指针或“拥有”某种隐式状态(存储在其他位置)的构造函数主体-通常意味着begin / acquire函数调用必须与end / release调用匹配-可能导致异常安全问题,但实际的问题在那里无法通过类正确管理资源。例如,如果unique_ptr
在构造函数中用替换原始指针,unique_ptr
则需要时将自动调用的析构函数。
人们还有其他原因喜欢偏爱最小的构造函数。一种是因为存在样式规则,所以许多人认为构造函数调用很便宜。拥有一个但仍具有很强不变性的方法是拥有一个单独的factory / builder类,该类具有弱化的不变性,并使用(可能有许多)正常的成员函数调用来设置所需的初始值。拥有所需的初始状态后,将该对象作为参数传递给具有强不变性的类的构造函数。这样可以“窃取”弱不变性对象的胆量-移动语义-这是一种廉价的(通常是noexcept
)操作。
当然,您可以将其包装在一个make_whatever ()
函数中,因此该函数的调用者无需查看弱化不变类实例。
main
功能或静态/全局变量之前拥有所有者。使用分配的对象new
,不拥有,直到您分配的责任,但智能指针拥有他们引用堆中分配的对象和容器拥有自己的数据结构。所有者可以选择早期删除,由所有者的析构函数最终负责。