“一切以简单为美“
C++中的类型繁多,其中包括(具体)类、基类、接口、结构、枚举、数组等。在此规范中,我们不详细讨论抽象类和接口,因为这两个类型属于一个特殊的逻辑分组,和扩展性有关,我们在扩展性设计规范中进行讨论。
任何的编程语言都可以看成是一个类型系统。在这个类型系统中,每个类型都扮演这各自的职责,各有其意义:
n (具体)类:在遵循某个特定的开发封闭原则的前提下的对行为和属性的封装。使用class声明。
n 基类:在遵循某个特定的开发封闭原则的前提下的对行为和属性的适度封装。使用class声明。
n 接口:抽象了某个/某些特定的行为,为对象提供了一种抽象的分类准则,它应该是基本稳定的。使用interface声明。
n 结构:用于定义小而简单的类型。使用struct声明。
n 枚举:用于定义一小组值,这一小组值代表着一个逻辑分类。如一星期中的每天。
在类型系统中不同的类型适用于不同的用途,因此遵循着不同的规则,代表着不同的意义。
l 推荐使用基类,如果:
a. 实现某个特定的接口(可选)
b. 对于子类,它的确具有抽象意义
c. 它通过与子类共享成员(属性、函数)或/和定义虚函数来实现抽象
d. 它具有/不具有状态
l 推荐使用抽象类,如果:
e. 具有基类的所有特征
f. 不能被实例化(至少具有一个纯虚函数)
l 推荐使用接口,如果:
a. 它代表了一类行为的抽象,这样的一类行为可以作为一种分类规则区别其他不同类的对象
b. 它的定义应该是基本稳定的,否则考虑使用抽象类而不是接口
c. 它定义功能而不是实现功能,不具有任何状态
d. 精心定义的接口只做一件事情
l 推荐使用结构,如果:
a. 它在逻辑上代表一个独立的值,与基本类型(int、double等)相似
b. 它不需要虚函数(包括虚析构函数)
c. 它总是在栈中被实例化,实例比较小而且生命周期比较段或经常被内嵌在其他对象中
l 推荐使用静态类,如果你设计的类具有下面的特征:
a. 它定义的所有的函数都是public static的
b. 它不需要被实例化
c. 它往往被用于定义一些帮助函数或基于简单性的考虑来整合某个特定的功能。
√ 要求区分接口、结构和类:
结构
使用struct关键字声明,用于定义以数据为中心的实体类型,如
struct PageSetting。
接口
使用interface(即struct)关键字声明,用于定义抽象的行为集合的实体类型,
如interface IDrawing。
类
使用class关键字声明,用于定义以行为为中心的实体类型,如
class TokenParser。
× 不要使用private和protected继承,除非万不得已。
例如,
//不好的做法:使用private继承来实现组合
class A{}
class B : private A{}
//好的做法:使用private成员来实现组合
class A{}
class B
{
private:
A m_a;
}
× 不要使用virutal继承,除非万不得已。
例如,
//不好
class Base1{}
class Base21 : virtual public Base1{}
class Base22 : virtual public Base1{}
class Base3 : public Base21,public Base22{}
l 推荐不要过度使用多继承,特别是实现继承。
√ 要求用小类代替巨类
小类更易于编写,更易于保证正确、测试和使用。而大类承担太多职责,削弱了封装性。
l 推荐用组合代替继承
避免继承带来的重负:继承是C++中第二紧密地耦合关系,仅次于友元关系。软件
工程的原则之一就是减少耦合。在适当的时候,应该使用组合代替继承。
例外:
如果需要改写基类的虚函数;
如果需要访问基类的保护成员;
如果需要控制多态;
如果需要在基类之前构造已使用过的对象,或在基类之后销毁此对象。
× 不要公开内部数据
数据隐藏是强大的抽象方式,也是强大的模块化机制。应该避免将内部数据句柄/ 指针暴露给外部。
例如,
class Component
{
public:
char* GetBuffer() {return m_buffer;}//不好
const char* GetBuffer()const{return m_buffer;}//好
private:
char* m_buffer;
}
l 推荐不要在抽象类中定义任何数据成员。
√ 要求在抽象类中定义protect而非public/private的构造函数。
例如,
class AddinBase
{
public:
virtual void Authorizing() = 0;
protected:
AddinBase(){}
}
l 推荐使用接口来定义抽象类的行为。
例如,
class IComponent
{
virtual void AddControl() = 0;
virtual void PendingModification() = 0;
}
class Component : public IComponent
{
public:
virtual void AddControl(){…}
virtual void PendingModification() = 0;
}
√ 要求一个接口只做一件事情。
× 不要定义接口,如果这个接口定义的功能很不稳定。
× 不要在接口中定义冗余的、存在二义性的pure virtual函数。
例如,
interface ILayout
{
//返回页数,页索引从0开始
virtual int GetActivePage () const = 0;
//返回传递给UI参数的页数,页索引从1开始,返回值= GetActivePage()+1
virtual int GetUI ActivePage () const = 0; //冗余
}
× 不要使用C风格的定义方式。
例如,
//不好
typedef struct tagColorSwatch
{
…
} ColorSwatch;
//好
struct ColorSwatch
{
…
};
√ 要求为结构提供一个默认的构造函数。
√ 要求为拷贝构造函数设为私有/保护成员,如果不需要拷贝构造函数。
例如,
struct ExportConfiguration
{
protected:
ExportConfiguration(ExportConfiguration& other){}
};
√ 要求为结构提供拷贝构造函数,如果:默认的拷贝构造函数的行为不是所需要的。
√ 要求为结构重载operator=,如果:默认的operator=行为不是所需要的。
√ 要求为结构重载operator==,operator!=,如果:默认的operator==,operator!=行为不是所需要的。
例如,
struct TextProperties
{
const wchar_t* FontName;
bool Bold;
bool Italic;
bool operator==(TextProperties& other)
{
return (Bold == other.Bold) &&
(Italic == other.Italic) &&
(strcmp(FontName, other. FontName) == 0) ) ;
}
};
× 不要设计面面俱到、非常灵活的结构。
例如,
//不好
PageCombineMergeSetting
{
const wchar_t* SourceFilename;
int SourcePage;
bool IsMerge;
bool IsCombine;
Graph::CdRect TargetRectangle; //当IsMerge==true时有效
Graph::CdRect TargetPage; //当IsCombine==true时有效
}
//好,改写为两个结构
struct MergeSetting
{
const wchar_t* SourceFilename;
int SourcePage;
Graph::CdRect TargetRectangle;
}
struct CombineSetting
{
const wchar_t* SourceFilename;
int SourcePage;
Graph::CdRect TargetPage;
}
l 推荐结构中元素的个数应适中。若结构中元素个数过多可考虑依据某种原则把元素组成不同的子结构,以减少原结构中元素的个数。
l 推荐仔细设计结构中元素的布局与排列顺序,使结构容易理解、节省占用空间,并减少引起误用现象。
l 推荐使用__declspec(align(x))方式定义结构的字节对齐方式:
例如,
//定义一个8字节对齐的结构
__declspec(align(8))
struct A{
double a,
int b;
}
× 不要使用C风格的定义方式。
例如,
//不好
typedef enum tagCOLORSWATCHTYPE
{
…
} COLORSWATCHTYPE;
//好
enum kColorSwatchType
{
…
};
√ 要求优先使用枚举而不要使用静态常量或宏定义。
例如,
//不好
struct ApplicationInfo
{
static const int UnknownProduct = 0;
static const int BusinessProduct = 1;
static const int NewsProduct = 2;
…
};
#define FreeProduct 3
//好
enum kProductType
{
kProductType_Unknown,
kProductType_Business,
kProductType_News,
kProductType_Free
}
× 不要把枚举用于开放的集合。
例如,操作系统的版本,朋友的名字等。
× 不要把sentinel值包含在枚举值中。
例如,
//好
enum kDeskType
{
kDeskType_Unknown = 0,
kDeskType_Circular = 1,
kProductType_Rectangular = 2,
kProductType_LastValue = 2//不好,不需要定义这个枚举值
}
√ 要求为简单枚举类型提供零值。
例如,
enum kCompressionType
{
kCompressionType_None
kCompressionType_GZip,
kCompressionType_Deflate
}
enum kRequestType
{
kRequestType_Error,
kRequestType_Warning,
kRequestType_Information
}
√ 要求使用复数名词/名词短语来命名标记枚举。
例如,
enum kFileShareModes
{
kFileShareModes_Read = 1,
kFileShareModes_Write = 2,
kFileShareModes_ReadWrite = kFileShareModes_Read| kFileShareModes_Write,
}
本文介绍了C++中的类型设计规范,包括类、结构、接口和枚举的使用场景和设计原则。推荐根据对象的特性选择合适的类型,如使用基类表示抽象意义和共享成员,使用接口定义稳定的行为,使用结构定义小而简单的类型,使用枚举表示逻辑分类。此外,还强调了结构和类的默认构造函数、拷贝构造函数、赋值运算符和比较运算符的使用,以及接口和抽象类的设计注意事项。
277

被折叠的 条评论
为什么被折叠?



