《Google C++编码规范》读书笔记第三章:类

《Google C++编码规范》第三章:类

类是C++中的基本单元。

一.构造函数的职责(constructor)

在构造函数执行那些trivial的初始化,可能的话,使用Init()函数执行那些non-trivial的初始化。
定义:执行初始化操作。
优点:排版方便,无需担心类是否初始化。
缺点:
- 构造函数中不易报告错误,不能使用异常
- 操作失败会引起对象初始化失败,导致不明确的状态
- 构造函数调用虚函数,虚函数并不会具有多态性,具体见《Effective C++》第二章条款9
- 如果使用了全局变量,构造函数在main()函数之前调用则有可能破坏构造函数中暗含的假设条件。

二.默认构造函数(Default Constructor)

一个类定义了数据成员,但是没有其它构造函数,需要定义一个默认构造函数,否则编译器会为其自动产生一个默认构造函数(编译器需要的情况下)。
定义:新建一个没有参数的对象时,默认构造函数被调用;另外,当使用new[],默认构造函数总是被调用。
优点:默认初始化。
缺点:对代码编写者来说,这是多余的工作。
如果类中定义了数据成员,需要定义一个默认构造函数,初始化对象,使对象的内部状态一致(internal state)。
编译器因为需要的情况产生的默认构造函数,是不会为类的数据成员初始化的,可能只是分配内存

三.明确的构造函数(Explicit Constructor)

对单参数的构造函数使用explicit关键字。
定义:只有一个参数的构造函数通常用于转换(conversion),为了避免隐式类型转换,可以将其声明为explicit,参见条款五.对定制的类型转换函数保持警觉
优点:避免不合时宜的转换。
缺点:无。

四.拷贝构造函数(Copy Constructor)

拷贝类对象的时候使用拷贝构造函数。
定义:通过拷贝构造函数来拷贝新建对象。
优点:C++对象中的隐式拷贝是导致很多性能问题和bugs的根源,拷贝构造函数降低了代码的可读性,相比于按引用传递,跟踪按值传递更加困难,对象修改的地方变得更加难以琢磨。
有些类是不需要拷贝构造函数或者拷贝复制运算符,如果不主动声明,编译器也会为你自动生成。
这时的方法:应该明确拒绝之
这里介绍使用宏DISALLOW_COPY_AND_ASSIGN:

#define DISALLOW_COPY_AND_ASSIGN \
    TypeName(const TypeName&);   \
    void operator=(const TypeName&)
class Foo {
public:
    Foo();
    ~Foo();
private:
    //注意宏在privateDISALLOW_COPY_AND_ASSIGN(Foo);
}

另一种方法在上面的链接中也提到了:private继承自一个Uncopyable类。

关于构造函数更加具体的含义,参考构造函数语义学

五.结构体和类量(Struct VS Class)

仅当只有数据时使用struct,其它情况均使用class。
struct和class的含义基本一致,唯一不太一样的是默认访问权限和默认继承类型:

关键字默认访问权限默认继承类型
structpublicpublic
classprivateprivate

另外还有一点:template并不兼容C,因此template<> struct是错误的。
如果与STL结合,对于仿函数(functors)和特性( traits)可以不用class 而是使用struct。

六.继承(Inheritance)

使用组合通常(composition)比继承更为合适,如果使用继承,只是用公共继承。
定义:在C++中,继承适用于两种场合:实现继承,子类继承父类的实现代码;接口继承,子类继承父类的接口/方法。
优点:通过继承原封不动地重用了基类的代码,有效地减少了代码量。由于继承是编译器声明,编码者和编译器都能发现错误。
缺点:对于实现继承,由于实现子类的代码在父类和子类间拓展,理解起来更困难。
结论:
继承必须是public,public继承模塑的是“is a”关系,如果要采用私有继承的话,应该采取包含基类实例作为成员的方式作为替代。具有虚函数的类,析构函数应该也是虚函数。

七.多重继承(multiple Inheritance)

定义:多重继承允许子类有多个基类,要将作为纯接口的基类和具有实现的基类区分开来。
优点:相比较于单继承,多重继承可以更多地复用代码。
缺点:正真需要用到多重继承的情况很少,多重继承看起来是比较不错的方案,但是通常可以找到更加明确、清晰的不同的解决方案。

八.接口(Interface)

接口是指满足特定条件的类,命名以Interface为后缀(非必需)。
定义:满足下列条件的类称为纯接口:
- 只有纯虚函数和静态函数(析构函数除外)
- 没有非静态数据成员
- 没有定义任何构造函数。如果有,也不含参数,并且为protected
- 如果是子类,也只能继承满足上述条件并以Interface为后缀的类

接口类不能被实例化,因为声明了纯虚函数。为了确保接口类被正确析构,必须声明虚析构函数。
优点:以Interface为后缀让其他人知道不能为该接口类增加实现函数或者非静态数据成员,这一点对多重继承尤为重要。
缺点:Interface为后缀增加了类名长度,同时,接口作为实现细节不应该暴露给客户。
结论:只有满足上述结论,类才能以Interface为后缀,反过来则不一定。

九.操作符重载

除了特定条件下,不要重载操作符。
定义:一个类可以定义如+,-,/等操作符,使用其可以像内建类型一样直接使用。
优点:使代码看起来像内建类型一样直观。
缺点:
- 混淆直觉,让你误以为一些耗时的操作和内建操作一样轻巧。
- 查找重载运算符的调用更加困难,查找Equal()显然比查找调用==容易的多。
- 有的操作符可以对指针进行操作,容易导致bugs,例如Foo + 4&Foo + 4可能做的是完全不同的另一件事,但是编译器不会报错,很难调式。
- 重载还可能带来副作用:比如重载&的类不能被前置声明。

结论:不要重载运算符,尤其是赋值运算,如果需要,可以定义Equal()等函数。
极少情况下需要重载操作符与标准衔接,例如:operator<<(ostream&, const T&),声明容器时,应该创建相等判断和大小比较的仿函数类。

十.存取控制(Assess Control)

将数据成员私有化,并提供相关的存取函数。存取函数一般定义在内联头文件中。
如定义变量foo以及取值函数foo()和赋值函数set_foo()

十一.声明次序(Declaration Order)

在类中使用特定的声明次序,public在private前面,成员函数在数据成员前边
定义次序一般如:public;protected; private
在每一块中的次序如下:
- typedef和enum
- 常量
- 构造函数
- 析构函数
- 成员函数,含静态成员函数
- 数据成员,含静态数据成员

DISALLOW_COPY_AND_ASSIGN置于private:块后,作为类的最后部分。
源文件中的函数定义尽量和声明次序一致。
不要将大型函数内联到类的定义中,通常,只有那些没有特别意义的或者性能要求高的,并且是比较短小的函数才被定义为内联函数。

十二.编写短小的函数(Write Short Functions)

长函数有时候是恰当的,因此对于函数长度没有严格限制。如果函数超过40行,在不影响程序结构的前提下可以将其分割一下。
函数尽量短小、简单,便于他人阅读和修改代码。

主要参考自《Google C++编码规范》中文版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值