副标题#e#
关于类的留意事项,总结一下:1. 不在结构函数中做太多逻辑相关的初始化; 2. 编译器提供的默认结构函数不会对变量举办初始化,假如界说了其他结构函数,编译器不再提供,需要编码者自行提供默认结构函数;3. 为制止隐式转换,需将单参数结构函数声明为explicit;……
类
类是C++中根基的代码单位,自然被遍及利用。本节罗列了在写一个类时要做什么、不要做什么。
1. 结构函数(Constructor)的职责
结构函数中只举办那些没有实际意义的(trivial,译者注:简朴初始化对付措施执行没有实际的逻辑意义,因为成员变量的“有意义”的值大多不在结构函数中确定)初始化,大概的话,利用Init()要了解合初始化为有意义的(non-trivial)数据。
界说:在结构函数中执行初始化操纵。
利益:排版利便,无需担忧类是否初始化。
缺点:在结构函数中执行操纵引起的问题有:
1) 结构函数中不易陈诉错误,不能利用异常。
2) 操纵失败会造成工具初始化失败,引起不确定状态。
3) 结构函数内挪用虚函数,挪用不会派发到子类实现中,纵然当前没有子类化实现,未来仍是隐患。
4) 假如有人建设该范例的全局变量(固然违背了上节提到的法则),结构函数将在main()之前被挪用,有大概粉碎结构函数中暗含的假设条件。譬喻,gflags尚未初始化。
结论:假如工具需要有意义的(non-trivial)初始化,思量利用别的的Init()要领并(或)增加一个成员标志用于指示工具是否已经初始化乐成。
2. 默认结构函数(Default Constructors)
假如一个类界说了若干成员变量又没有其他结构函数,需要界说一个默认结构函数,不然编译器将自动出产默认结构函数。
界说:新建一个没有参数的工具时,默认结构函数被挪用,当挪用new[](为数组)时,默认结构函数老是被挪用。
利益:默认将布局体初始化为“不行能的”值,使调试越发容易。
缺点:对代码编写者来说,这是多余的事情。
结论:
假如类中界说了成员变量,没有提供其他结构函数,你需要界说一个默认结构函数(没有参数)。默认结构函数更适合于初始化工具,使工具内部状态(internal state)一致、有效。
提供默认结构函数的原因是:假如你没有提供其他结构函数,又没有界说默认结构函数,编译器将为你自动生成一个,编译器生成的结构函数并不会对工具举办初始化。
假如你界说的类担任现有类,而你又没有增加新的成员变量,则不需要为新类界说默认结构函数。
#p#副标题#e#
3. 明晰的结构函数(Explicit Constructors)
对单参数结构函数利用C++要害字explicit。
界说:凡是,只有一个参数的结构函数可被用于转换(conversion,译者注:主要指隐式转换,下文可见),譬喻,界说了Foo::Foo(string name),当向需要传入一个Foo工具的函数传入一个字符串时,结构函数Foo::Foo(string name)被挪用并将该字符串转换为一个Foo姑且工具传给挪用函数。看上去很利便,但假如你并不但愿如此通过转换生成一个新工具的话,贫苦也随之而来。为制止结构函数被挪用造成隐式转换,可以将其声明为explicit。
利益:制止不适时宜的调动。
缺点:无。
结论:
所有单参数结构函数必需是明晰的。在类界说中,将要害字explicit加到单参数结构函数前:explicit Foo(string name);
破例:在少数环境下,拷贝结构函数可以不声明为explicit;特意作为其他类的透明包装器的类。雷同破例环境应在注释中明晰说明。
4. 拷贝结构函数(Copy Constructors)
仅在代码中需要拷贝一个类工具的时候利用拷贝结构函数;不需要拷贝时应利用DISALLOW_COPY_AND_ASSIGN。
界说:通过拷贝新建工具时可利用拷贝结构函数(出格是工具的传值时)。
利益:拷贝结构函数使得拷贝工具越发容易,STL容器要求所有内容可拷贝、可赋值。
缺点:C++中工具的隐式拷贝是导致许多机能问题和bugs的来源。拷贝结构函数低落了代码可读性,对比按引用通报,跟踪按值通报的工具越发坚苦,工具修改的处所变得难以捉摸。
结论:
大量的类并不需要可拷贝,也不需要一个拷贝结构函数或赋值操纵(assignment operator)。不幸的是,假如你不主动声明它们,编译器会为你自动生成,并且是public的。
可以思量在类的private中添加空的(dummy)拷贝结构函数和赋值操纵,只有声明,没有界说。由于这些空措施声明为private,当其他代码试图利用它们的时候,编译器将报错。为了利便,可以利用宏DISALLOW_COPY_AND_ASSIGN:
// 克制利用拷贝结构函数和赋值操纵的宏
// 应在类的private:中利用
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&); \
void operator=(const TypeName&)
class Foo {
public:
Foo(int f);
~Foo();
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
};
#p#分页标题#e#
如上所述,绝大大都环境下都应利用DISALLOW_COPY_AND_ASSIGN,假如类确实需要可拷贝,应在该类的头文件中说明原由,并适当界说拷贝结构函数和赋值操纵,留意在operator=中检测自赋值(self-assignment)环境。
在将类作为STL容器值得时候,你大概有使类可拷贝的激动。雷同环境下,真正该做的是利用指针指向STL容器中的工具,可以思量利用std::tr1::shared_ptr。
5. 布局体和类(Structs vs. Classes)
仅当只有数据时利用struct,其它一概利用class。
在C++中,要害字struct和class险些寄义等同,我们为其工钱添加语义,以便为界说的数据范例公道选择利用哪个要害字。
struct被用在仅包括数据的消极工具(passive objects)上,大概包罗有关联的常量,但没有存取数据成员之外的函数成果,而存取成果通过直接会见实现而无需要领挪用,这儿提到的要领是指只用于处理惩罚数据成员的,如结构函数、析构函数、Initialize()、Reset()、Validate()。
假如需要更多的函数成果,class更适合,假如不确定的话,直接利用class。
假如与STL团结,对付仿函数(functors)和特性(traits)可以不消class而是利用struct。
留意:类和布局体的成员变量利用差异的定名法则。
6. 担任(Inheritance)
利用组合(composition,译者注,这一点也是GoF在《Design Patterns》里重复强调的)凡是比利用担任更适宜,假如利用担任的话,只利用民众担任。
界说:当子类担任基类时,子类包括了父基类所有数据及操纵的界说。C++实践中,担任主要用于两种场所:实现担任(implementation inheritance),子类担任父类的实现代码;接口担任(interface inheritance),子类仅担任父类的要领名称。
利益:实现担任通过原封不动的重用基类代码淘汰了代码量。由于担任是编译时声明(compile-time declaration),编码者和编译器都可以领略相应操纵并发明错误。接口担任可用于措施上加强类的特定API的成果,在类没有界说API的须要实现时,编译器同样可以侦错。
缺点:对付实现担任,由于实现子类的代码在父类和子类间延展,要领略其实现变得越发坚苦。子类不能重写父类的非虚函数,虽然也就不能修改其实现。基类也大概界说了一些数据成员,还要区分基类的物理表面(physical layout)。
结论:
所有担任必需是public的,假如想私有担任的话,应该采纳包括基类实例作为成员的方法作为替代。
不要过多利用实现担任,组合凡是更符合一些。尽力做到只在“是一个”("is-a",译者注,其他"has-a"环境下请利用组合)的环境下利用担任:假如Bar简直“是一种”Foo,才令Bar是Foo的子类。
须要的话,令析构函数为virtual,须要是指,假如该类具有虚函数,其析构函数应该为虚函数。
译者注:至于子类没有特别数据成员,甚至父类也没有任何数据成员的非凡环境下,析构函数的挪用是否须要是语义争论,从编程设计类型的角度看,在含有虚函数的父类中,界说虚析构函数绝对须要。
限定仅在子类会见的成员函数为protected,需要留意的是数据成员应始终为私有。
当重界说派生的虚函数时,在派生类中明晰声明其为virtual。基础原因:假如漏掉virtual,阅读者需要检索类的所有祖先以确定该函数是否为虚函数(译者注,固然不影响其为虚函数的本质)。
7. 多重担任(Multiple Inheritance)
真正需要用到多重实现担任(multiple implementation inheritance)的时候很是少,只有当最多一个基类中含有实现,其他基类都是以Interface为后缀的纯接口类时才会利用多重担任。
界说:多重担任答允子类拥有多个基类,要将作为纯接口的基类和具有实现的基类区别开来。
利益:对比单担任,多重实现担任可令你重用更多代码。
缺点:真正需要用到多重实现担任的时候很是少,多重实现担任看上去是不错的办理方案,凡是可以找到越发明晰、清晰的、差异的办理方案。
结论:只有当所有超类(superclass)除第一个外都是纯接口时才气利用多重担任。为确保它们是纯接口,这些类必需以Interface为后缀。
留意:关于此法则,Windows下有种破例环境(译者注,将在本译文最后一篇的法则破例中叙述)。
8. 接口(Interface)
接口是指满意特定条件的类,这些类以Interface为后缀(非必须)。
界说:当一个类满意以下要求时,称之为纯接口:
1) 只有纯虚函数("=0")和静态函数(下文提到的析构函数除外);
2) 没有非静态数据成员;
3) 没有界说任何结构函数。假如有,也不含参数,而且为protected;
4) 假如是子类,也只能担任满意上述条件并以Interface为后缀的类。
#p#分页标题#e#
接口类不能被直接实例化,因为它声明白纯虚函数。为确保接口类的所有实现可被正确销毁,必需为之声明虚析构函数(作为第1条法则的破例,析构函数不能是纯虚函数)。详细细节可参考Stroustrup的《The C++ Programming Language, 3rd edition》第12.4节。
利益:以Interface为后缀可令他人知道不能为该接口类增加实现函数或非静态数据成员,这一点对付多重担任尤其重要。别的,对付Java措施员来说,接口的观念已经深入人心。
缺点:Interface后缀增加了类名长度,为阅读和领略带来未便,同时,接口特性作为实现细节不该袒露给客户。
结论:。只有在满意上述需要时,类才以Interface末了,但反过来,满意上述需要的类未必必然以Interface末了。
9. 操纵符重载(Operator Overloading)
除少数特定情况外,不要重载操纵符。
界说:一个类可以界说诸如+、/等操纵符,使其可以像内建范例一样直接利用。
利益:使代码看上去越发直观,就像内建范例(如int)那样,重载操纵符使那些Equals()、Add()等黯淡无光的函数名好玩多了。为了使一些模板函数正确事情,你大概需要界说操纵符。
缺点:固然操纵符重载令代码越发直观,但也有一些不敷
1) 夹杂直觉,让你误觉得一些耗时的操纵像内建操纵那样轻巧;
2) 查找重载操纵符的挪用处越发坚苦,查找Equals()显然比同等挪用==容易的多;
3) 有的操纵符可以对指针举办操纵,容易导致bugs,Foo + 4做的是一件事,而&Foo + 4大概做的是完全差异的另一件事,对付二者,编译器都不会报错,使其很难调试;
4) 重载尚有令你受惊的副浸染,好比,重载操纵符&的类不能被前置声明。
结论:
一般不要重载操纵符,尤其是赋值操纵(operator=)较量阴险,应制止重载。假如需要的话,可以界说雷同Equals()、CopyFrom()等函数。
然而,少少数环境下需要重载操纵符以便与模板或“尺度”C++类跟尾(如operator<<(ostream&, const T&)),假如被证明是合法的尚可接管,但你要尽大概制止这样做。尤其是不要仅仅为了在STL容器中作为key利用就重载operator==或 operator<,取而代之,你应该在声明容器的时候,建设相等判定和巨细较量的仿函数范例。
有些STL算法确实需要重载operator==时可以这么做,不要忘了提供文档说明原因。
参考拷贝结构函数和函数重载。
10. 存取节制(Access Control)
将数据成员私有化,并提供相关存取函数,如界说变量foo_及取值函数foo()、赋值函数set_foo()。
存取函数的界说一般内联在头文件中。
参考担任和函数定名。
11. 声明序次(Declaration Order)
在类中利用特定的声明序次:public:在private:之前,成员函数在数据成员(变量)前。
界说序次如下:public:、protected:、private:,假如那一块没有,直接忽略即可。
每一块中,声明序次一般如下:
1) typedefs和enums;
2) 常量;
3) 结构函数;
4) 析构函数;
5) 成员函数,含静态成员函数;
6) 数据成员,含静态数据成员。
宏DISALLOW_COPY_AND_ASSIGN置于private:块之后,作为类的最后部门。参考拷贝结构函数。
.cc文件中函数的界说应尽大概和声明序次一致。
不要将大型函数内联到类的界说中,凡是,只有那些没有出格意义的可能机能要求高的,而且是较量短小的函数才被界说为内联函数。更多细节参考译文第一篇的内联函数。
12. 编写短小函数(Write Short Functions)
倾向于选择短小、凝练的函数。
长函数有时是得当的,因此对付函数长度并没有严格限制。假如函数高出40行,可以思量在不影响措施布局的环境下将其支解一下。
纵然一个长函数此刻事情的很是好,一旦有人对其修改,有大概呈现新的问题,甚至导致难以发明的bugs。使函数只管短小、简朴,便于他人阅读和修改代码。
#p#分页标题#e#
在处理惩罚代码时,你大概会发明巨大的长函数,不关键怕修改现有代码:假如证实这些代码利用、调试坚苦,可能你需要利用个中的一小块,思量将其支解为越发短小、易于打点的若干函数。
______________________________________
译者:关于类的留意事项,总结一下:
1. 不在结构函数中做太多逻辑相关的初始化;
2. 编译器提供的默认结构函数不会对变量举办初始化,假如界说了其他结构函数,编译器不再提供,需要编码者自行提供默认结构函数;
3. 为制止隐式转换,需将单参数结构函数声明为explicit;
4. 为制止拷贝结构函数、赋值操纵的滥用和编译器自动生成,可今朝声明其为private且无需实现;
5. 仅在作为数据集适时利用struct;
6. 组合>实现担任>接口担任>私有担任,子类重载的虚函数也要声明virtual要害字,固然编译器答允不这样做;
7. 制止利用多重担任,利用时,除一个基类含有实现外,其他基类均为纯接口;
8. 接口类类名以Interface为后缀,除提供带实现的虚析构函数、静态成员函数外,其他均为纯虚函数,不界说非静态数据成员,不提供结构函数,提供的话,声明为protected;
9. 为低落巨大性,只管不重载操纵符,模板、尺度类中利用时提供文档说明;
10. 存取函数一般内联在头文件中;
11. 声明序次:public->protected->private;