赞
踩
1.常量
1.1使用常量取代宏
#define MAX (20) //不好的例子
const int MAX =20 //好的例子
1.2一组相关的常量应定义为枚举
1.2.1编译前检查变量
1.2.2视情况赋值
1.2.3尽量避免枚举重复,如必须重复用已经定义的枚举来修饰
enum Color{black,blue}
1.3不相关的常量,即使取值不一样,也必须分别定义(尽可能使用const)
2.初始化和类型转换
2.1禁止用memcpy、memset初始化非POD对象
(POD基础数据类型如int char float double)
2.2变量使用时声明并初始化
string name;
name="zhangsan" //错误
string name("zhansgan") //正确
2.3避免构造函数做复杂初始化,或者使用"init"函数,如下情况可以使用
2.3.1需要提供初始化返回信息
2.3.2数据成员可能会抛异常
2.3.3数据成员初始化失败会造成类对象初始化失败,引起不确定状态
2.3.4数据初始化依赖this指针:构造函数没有结束,对象没有构造出来,构造函数不能使用this成员
2.3.5初始化过程需要调用虚函数。在构造函数和析构函数中调用虚函数,会导致未定义异常
2.3.6例子
class CPPRule
{
public:
CPPRule():size_(0),res(null){};
long init(int size)
{
//分配资源给res
}
private:
int size_;
Res res;
}
//正确
CPPRule A;
A.init(100);
2.4初始化列表要求严格按照成员声明顺序初始化他们
2.5明确外部依赖关系的全局与静态对象的初始化顺序,可用单例模式或者吧依赖关系的全局对象放在一个文件中来明确初始化顺序
2.6使用C++风格的类型转换,不要使用C风格的类型转换
2.6.1 A* a = (A*) b;//编译不报错,运行会奔溃 (A:public B)
2.6.2 A* a = dynamic_cast<A*> (b)//明确知道指向哪里
2.7避免使用reinterpret_cast
2.8避免使用const_cast(强制去除const属性)
2.9使用虚函数替换dynamic_cast
3.函数
3.1内联函数
3.1.1内联函数小于10行
3.1.2用内联函数替代函数宏
3.1.3内联函数应该放在头文件,如果在类外面要添加inline
3.1.4内联函数的实现放在独立的文件(放单独的头文件中使用扩展名.inl)
3.2函数参数
3.2.1入参尽量用const引用取代指针,使用const避免空指针也避免编译器检测,const让代码阅读者知道该参数不需要修改
3.2.2消除未使用的函数参数
3.2.3尽量少用缺省参数
3.3函数指针
3.3.1尽可能少用函数指针,因为难以维护和难以理解
4.类
4.1类的设计
4.1.1类职责单一,用小类取代巨类,一个类有10个以上数据成员,类的职责可能过多
4.2隐藏信息(封装时面向对象设计和变成的核心思想)
4.2.1尽量减少全局和共享数据
4.2.2禁止成员函数返回成员写的引用或者指针
4.2.3将数据成员设为私有的(struct除外),并提供相关存取函数
4.2.4避免每个类数据成员提供函数
4.2.5运行时多态,将内部实现(派生类提供)与对外接口(基类提供)分离
4.3尽量使接口正交,少而完备
4.3.1应该围绕一个核心去定义接口,提供服务,与其他类合作,从而易于实现、理解、使用、测试、和维护
正交是指避免一个接口功能覆盖另外一个接口功能。接口函数太多,会难以理解使用和维护。如果一个类包含20个以上非私有成员函数,类的几口能不不够精简
4.4块间对外接口类不要暴露私有和保护成员
4.4.1使用纯虚类作为接口类,用实现类去完成,使用者只看到接口类缺点:
4.4.1.1代码复杂
4.4.1.2新增接口必须放在原有接口后面,不能改变原有接口的顺序。否则虚函数表的原因导致重新编译
4.4.2接口类使用PIMPL(只有一个指向实现类指针的私有成员),所有私有成员都封装在实现类中
4.4.2.1接口简单
4.4.2.2节省虚函数开销,有间接访问开销
4.4.2.3不会使代码重新编译
class Interface
{
public:
void function();
private:
Implementation* imp_;
};
class Implementation
{
public:
int i;
int j;
};
void Interface::function()
{
++imp_->i;
}
4.5避免成员函数返回成员科协的引用或者指针 const int A(){}
4.6禁止类之间循环依赖(增加耦合)
4.7将数据成员设为私有的,并提供相关存取函数
4.7.1非private成员破坏了类的封装性,导致类本身不知道数据成员被修改
4.7.2任何对类修改都会延申影响到使用该类的代码
4.8使用PIMPL模式,确保私有成员真正不可见
4.9.构造、赋值和析构
4.9.1包含成员变量的类,须定义构造函数或者默认构造函数
4.9.2避免隐式转换,将单参数构造函数声明为explicit(explicit关键字的作用就是防止类构造函数的隐式自动转换.) Foo(const string &name) 存在问题,所以禁止
4.9.3包含资源管理的类应自定义拷贝构造函数、赋值操作符和析构函数
4.9.3.1如何不需要拷贝构造函数可以private属性,让他们失效
4.9.3.2如果结构或对象中包含指针,自定义自己的拷贝函数和赋值符号避免野指针!!!
class A
{
public:
A()
{
iNum =0;
pGid= NULL;
}
~A()
{
if(pGid)
{
delete [] pGid;
}
}
private:
int iNum;
char *pGid;
A(const A& rs);
A& opertor=(const A& rs);
}
4.9.4让operator = 返回*this的引用
4.9.4.1String& String::opertor=(const String& rhs)
{
return *this;
}
String a,b,c;
a=b=c="HELLO";
4.9.5在operator=检查给自己赋值的情况
4.9.6在拷贝构造函数、赋值操作符对所有成员赋值
4.9.7通过基类指针来执行伤处操作时,基类的析构函数设为公有且虚拟的
4.9.7.1
class A
{
public:
vritual String one() =0;
}
class B :public A
{
public:
~B()
{
cout << "~B()"<<endl;
delete[] m_int;
}
std::String one()
{
return std::string("HELLO");
}
private:
int * m_int;
}
int main()
{
A *p = new B();
delete p;
return 0;
} //会出现内存泄露
4.9.8避免在构造函数和析构函数中调用虚函数
4.9.9拷贝构造函数和赋值操作符的参数定义成const引用类型
4.9.10在析构函数中集中释放资源
4.10继承
4.10.1用组合代替继承
4.10.1.1
class A{}
class B{}
class C{}
class D
{
private:
A a;
B b;
C c;
}
4.10.2 避免多重继承
4.10.3使用public 继承而不是private继承
4.10.4继承层次不超过4层,降低维护难度
4.10.5虚函数绝对不使用缺省参数值
4.10.6虚函数的缺省参数是在编译时刻决定的而不是运行时刻决定的(如果函数没有参数则在编译期间已经确定好值)
class A
{
public:
virtual void test(int a = 1)
{
std::cout << a<< std::endl;
};
};
class B :public A
{
public:
virtual void test(int a =2)
{
std::cout << a << std::endl;
}
};
int main()
{
A* a = new B();
B* b = new B();
a->test();
b->test();
while (1) {}
return 0;
}
4.10.6绝不重新定义继承而来的非虚函数,非虚函数无法动态绑定
4.10.7避免派生类中定义与基类同名但参数类型不同的函数
5.作用域、模板和C++其他特性
5.1使用名字空间进行归类,避免符号冲突
5.2不要在头文件或者#include 之前使用using指示符
5.3尽量少使用嵌套类
5.4尽可能不要使用局部类
5.5使用静态成员函数或名字空间内的非成员函数,避免使用全局函数
5.6避免class类型的全局变量,尽可能用单件模式
6.模板
6.1谨慎使用模板,只用模板的基础特性
6.2注意使用模板的代码膨胀
6.3模板石磊应该使用引用或者指针
6.4模板如果有约束条件,请在模板定义处显式说明
6.5两个模板之间接口尽量不要暴露模板
7.资源分配和释放
7.1明确动态内存的申请和释放
7.2明确operator new 行为哦和检查策略,在申请内存后,要立即检查是否为NULL或者进行捕获异常
7.3释放内存后,要立即将指针设置为NULL,防止产生野指针
7.4单个对象释放使用delete,数组对象释放使用delete[]
7.5释放结构(类)指针时,首先释放其成员指针的内存空间(只针对结构体)
7.5.1
struct A
{
ULONG a;
UCHAR * c;
}
void func()
{
A* aa=nullptr;
//申请
//释放
free(aa);
} //如果这样的话,aa->c 不会被删除
正确做法
free (aa->c);
free (aa);
7.6释放指针数组时,首先释放数组每个元素指针的内存
7.7不要返回局部对象指针,会产生野指针
7.8不要强制关闭线程,线程强制关闭,导致线程内部资源泄露。用事件或信号量通知线程,确保线程调用自身的退
出函数。线程死锁需要强制关闭的情况除外。
7.9使用new,delete的封装方法来分配和释放内存
说明:推荐使用如下宏,可以在一定程度上避免使用空指针,野指针的问题。
#define HW_NEW(var, classname) \
do { \
try \
{ \
var = new classname; \
} \
catch (...) \
{ \
var = NULL; \
} \
break; \
} while(0);
#define HW_DELETE_A(var) \
do \
{\
if (var != NULL) \
{ \
delete []var; \
var = NULL; \
} \
break; \
} while(NULL == var)
7.10避免在不同的模块中分配和释放内存在一个模块中分配内存,却在另外一个模块
中释放它,会使这两个模块之间产生远距离依赖,使程序变得脆弱。
7.11使用RAII特性来帮助追踪动态分配(对资源用类的操作)
7.11.1利用的就是C++构造的对象最终会被销毁的原则。RAII的做法是使用一个对象,
在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终
保持有效,最后在对象析构的时候,释放构造时获取的资源。
7.11.2如果把资源用类进行封装起来,对资源操作都封装在类的内部,在析构函数中
进行释放资源。当定义的局部变量的生命结束时,它的析构函数就会自动的被
调用,如此,就不用程序员显示的去调用释放资源的操作了。
7.11.3
class My_scope_lock
{
public:
My_scope_lock(LockType& _lock):m_lock(_lock)
{
m_lock.occupy();
}
~My_scope_lock()
{
m_lock.relase();
}
protected:
LockType m_lock;
}
bool class Data::Update()
{
My_scope_lock l_lock(m_mutex_lock);
if()
{
return false;
}
else
{
//execute
}
return true;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。