1,视C++为一种语言联邦,大致分为4个部分:
A)C。说到底C++仍是以C为基础。区块、语句、预处理器、内置数据类型、数组、指针等等统统来自C。
B)Object-Oriented C++。这部分也就是C with Classes所诉求的:classes(包括构造函数和虚构函数)、封装、继承、多态,虚函数等等。
C)Template C++。这是C++的范型编程部分,tamplates威力强大,它给我们带来了崭新的编程范型,也就是所谓的TMP模板元编程。
D)STL。STL是个template程序库,它对容器、迭代器、算法以及函数对象的规范有极佳的紧密配合与协调,然后template及程序库也可以其他想法建置出来。
2,尽量使用const,enum,inline代替#define
A)#define不被视为语言的一部分,属于预处理指令,编译不会计入符号表无法调试。
B)#define在预处理器处理阶段只做简单的替换,这将带来很多预期意外的行为。如
#define MAX(a, b) ((a)>(b)?(a):(b))
尽管上述宏定义已将变量用括号括起来了,但是还是不能避免MAX(++a, b+10)这样给a所带来的两次不是预期内的自增行为。以为替换为:
template<typename T>
inline T Max(const T& a, const T& b)
{
return a > b ? a : b;
}
3,尽可能使用const
A)修饰指针
char* p = “hello” //non-const pointer, non-const data
const char* p = “hello” //non-const pointer, const data
char* const p = “hello” //const pointer,non-const data
const char* const p = “hello’ //const pointer, const data
B)修饰迭代器
const std::vector<int>::iterator it = vec.begin(); //const pointer,non-const data
*it = 10 ; //no problem
it ++; //error
std ::vector<int>::const_iterator it = vec.begin() //non-pointer, const data
*it = 10; // error
it ++; //no problem
C)修饰函数参数、返回值、成员函数
class TextBlock {
public:
const char& operator[](std::size_t pos) const
{
return text[pos];
}
char & operator[](std::size_t pos)
{
return const_cast<char&>(static_cast<const TextBlock&>(*this)[pos]);
}
private:
char text[32];
};
4,确定对象被使用前已被初始化
A)区分变量初始化和变量赋值两者之间的区别
B)警惕在C++中类未初始化完成之前就使用的问题,因为无法确定类与类之间的初始化顺序
5,了解C++默默编写并调用那些函数
编译器可以暗自为class创建default构造函数,copy构造函数,copy assignment赋值操作符,以及析构函数.
6,若不想使用编译器自动生成的函数,就该明确拒绝
将copy构造函数和赋值操作符声明为private成员函数且不去实现它们.
7,为多态基类声明virtual析构函数
delete一个具有多态性质的基类指针是未定义的行为,这将导致派生类的析构函数无法正常调用.因为为具有多态性质的基类定义virtual析构函数.
8,别让异常逃离析构函数
在析构函数中发生的异常不允许扩散出去,应该捕获异常,并选择终止或吞下该异常.
9,绝不在构造或者析构函数中调用virtual函数
这绝对视一种诡异的行为...
10,令operator=返回一个reference to *this
这是一个实现的协议,为了兼容连锁赋值操作,就像这样:
Wiget& operator=(const Wiget& rhs)
{
//do something
return *this;
}
11,在operator=中处理”自我赋值”
潜在的自我赋值必然存在,而且未必能立马识别出来.既然实现了operator=就必然要考虑到自我赋值的情况,参见10点
Wiget& operator=(const Wiget& rhs)
{
if (this == &rhs)
return *this;
//do something
return *this;
}
12,复制对象时视勿忘其每一个成分
新增成员后忘记对拷贝构造函数和复制操作函数进行同步修改;
如果基类实现了复制操作函数,在派生类的复制操作函数应显示调用,以保证基类也被正确的复制.
13,以对象管理资源
A)为了防止内存泄露,请使用RAII对象,在构造函数中获取资源,在析构函数中释放资源,用栈中的局部变量管理堆内存。
B)std::auto_ptr只保存一份指针对象,赋值语句的右值将被置为NULL。而std::tr1::shared_ptr则是引用计数型智能指针,但是只能针对单个对象使用,对象数组应使用shared_array。
std::auto_ptr<Wiget> ap(new Wiget);
std::tr1::shared_ptr<Wiget> sp(new Wiget);
14,在资源管理类中小心copying行为
用来管理堆内存的RAII对象发生了复制行为会怎样?如果RAII对象是浅拷贝,这将简单的复制指针,当RAII对象析构的时候,指针指向的内存将被重复释放,如果RAII对象视深拷贝,这复制一份指针指向的内存,虽然正常但不是我们所要的。避免发生复制行为:
A)显试定义拷贝构造函数和赋值操作符函数,但不实现它,禁止复制行为。
B)使用复制增加引用计数的方式来确定何时内存应该被释放
不仅可以管理内存,还可以管理其它资源,如下:
class Lock {
public:
explicit Lock(Mutex* pm):mutextPtr(pm, unlock)
{
lock(mutexPtr.get());
}
private:
std::tr1::shared_ptr<Mutext> mutexPtr;
};
调用:
Mutex m;
{
Lock ml(&m); //进入临界区
..... //再也不用担心ml被复制了
}
局部变量被自动释放。shared_ptr自动调用删除器unlock,解锁临界区。
15,在资源管理类中提供对原始资源的访问
提供get方法,或者重载operator->,operator*,不提倡operator T() const,可能带来隐式转换。
16,成对使用new和delete要采用相同的形式
new对应delete,new[]对应delete[]。警惕typedef蒙蔽了你的双眼。如:
typedef int vec[100];
int *p = new vec;
delete p; //error
17,以独立的语句将newd对象置于shared_ptr之中
process(std::tr1::shared_ptr<Wiget> pW(new Wiget), f1());
上述函数参数做了3件事:new Wiget, f1(), 构造shared_ptr;顺序不能确定,假如f1()异常将导致new Wiget丢失。应该将智能指针构造独立出来:
std::tr1:;shared_ptr<Wiget> pW(new Wiget);
process(pW, f1());
18,让接口容易被正确使用,不易被误用
A)重载operator*时返回const 对象,禁止被当做左值使用。
B)确保接口能被正确的调用
C)谁使用谁负责的思想在跨DLL时行不通,new/delete成对使用将导致运行时错误,应使用shared_ptr提供的删除器,将内存管理职责收回。
19,设计Class犹如设计Type
A)新type的对象应该如何被创建和销毁?new/delete,new[]/delete[]
B)对象初始化和对象赋值又怎样的差别?不要混淆什么是初始化,什么是赋值
C)新type的对象被pass by value意味着什么?对象拷贝
D)什么是新type的合法值?约束成员的属性
E)新的type需要配合某个继承图系吗?注意多态应该实现virtual析构函数
F)新type需要什么样的转换?explicit 构造函数不容许隐式转换,但是数值类型例外
G)什么样的操作符和函数对新type而言是合理的?约束行为属性
H)什么样的标准函数应该驳回?约束class的默认行为
I)谁该取用新type的成员?类的封装和抽象
J)什么视新type的“未声明接口”?资源管理,效率,安全性定义
K)新的type有多么一般化?模板化,特化
L)你真的需要一个新的type?一个新的type以上都是要考虑的
20,宁以pass-by-reference-to-const替换pass-by-value
A)前者更加高效,避免了赋值类的开销,也避免了类被切割问题;
B)除了自定义类(也有例外),内置类型和STL迭代器、函数对象传值更加妥当。
21,返回对象时,别妄想返回其reference
局部变量和临时变量不能作为指针或者引用返回,其内存随作用域结束而释放。
22,将成员变量声明为private
所谓越是看不见牵扯越少,提供更好的封装性,数据一致性,弹性。
23,宁以non-member,non-friend替换member函数
24,若所有参数皆需类型转换,请为此采用non-member函数
佐证第23条,operator*实现两个不同版本哪个好?
版本1:
class Rational {
public:
const Rational operator*(const rational& rhs) const;
};
版本2:
class Rational {};
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs..., rhs...);
}
参考代码:
#include <stdio.h>
class Rational
{
public:
Rational();
Rational(int ca = 0, int cb= 0)
: a(ca), b(cb)
{}
void print()
{
printf("%d, %d\n", a, b);
}
public:
int a;
int b;
};
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.a + rhs.a, lhs.b + rhs.b);
}
int main(int argc, char* argv[])
{
Rational r1(1, 2);
Rational r2(3, 4);
Rational r3 = r1 * r2;
Rational r4 = r1 * 2;
Rational r5 = 2 * r1;
r1.print();
r2.print();
r3.print();
r4.print();
r5.print();
return 0;
}
25,考虑写一个不抛出异常的swap函数
正确的调用std::swap:
using std::swap;
swap(obj1, obj2);
26,尽可能的延后变量定义式的出现时间
为了改善程序效率,遵循使用时再定义的原则。
27,尽少做转型动作
const_cast将对象的常量性移除
static_cast强迫隐式转换
dynamic_cast执行安全向下转型,不建议使用通常使用virtual函数实现调用
reinterpret_cast执行低级转型
28,避免返回handles指向对象内部成分
将成员定义为private,由提供方法get出来返回成员的引用、指针或迭代器,这是自相矛盾的。
29,为“异常安全”而努力是值得的
异常安全函数会不泄露任何资源,不容许数据被破坏。
void PrettyMenu::changeBackgroud(std::istream& imgSrc)
{
lock(&mutex);
delete bgImage;
bgImage = new Image(imgSrc); //异常发生点
unlock(&mutex); //永远不会被unlock
}
void PrettyMenu::changeBackgroud(std::istream& imgSrc)
{
Lock ml(&mutex) //参看第14点定义
delete bgImage;
bgImage = new Image(imgSrc); //异常发生点
//函数返回就会unlock
}
30,透过了解inlining的里里外外
inline减少了函数调用的开销,什么时候申明为inline函数应该谨慎。
31,将文件间的编译依存关系降至最低
对于C++类而言,如果它的头文件变了,那么所有这个类的对象所在的文件都要重编,但如果它的实现文件(cpp文件)变了,而头文件没有变(对外的接口不变),那么所有这个类的对象所在的文件都不会因之而重编。因此,避免大量依赖性编译的解决方案就是:在头文件中用class声明外来类,用指针或引用代替变量的声明;在cpp文件中包含外来类的头文件。
32,确定你的public继承塑模出来视is-a关系
33,避免掩盖继承而来的名称
派生类会掩盖所有基类的同名函数,可使用using base::func;在派生类中可见。
34,区分接口继承和实现继承
pure virtual函数只具体指定接口继承
impure virtual函数具体指定接口继承及缺省实现继承
non-virtual函数具体指定接口继承以及强制性实现继承
35,考虑virtual函数以外的其它选择
A)使用non-virtual interface(NVI)手法
B)将virtual函数替换为“函数指针成员变量”
C)以tr1::function成员变量替换virtual函数
D)将一个继承体系内的virtual函数替换为另一个继承体系内的virtual函数
#include <stdio.h>
#include <tr1/memory>
#include <tr1/functional>
class GameCharacter
{
public:
void healthValue()
{
printf("before %s\n", __FUNCTION__);
doHealthValue();
printf("after %s\n", __FUNCTION__);
}
private:
virtual void doHealthValue()
{
printf("GameCharacter doHealthValue\n");
}
};
class Player : public GameCharacter
{
private:
virtual void doHealthValue()
{
printf("Player doHealthValue\n");
}
};
class GameCharacter2;
void defaultHealthCalc(const GameCharacter2& gc)
{
printf("%s\n", __FUNCTION__);
}
void dogHealthCalc(const GameCharacter2& gc)
{
printf("%s\n", __FUNCTION__);
}
class GameCharacter2
{
public:
typedef void (*HealthCalcFunc)(const GameCharacter2&);
explicit GameCharacter2(HealthCalcFunc hcf = defaultHealthCalc)
: m_healthFunc(hcf)
{}
void healthValue() const
{
m_healthFunc(*this);
}
private:
HealthCalcFunc m_healthFunc;
};
class GameCharacter3;
void defaultObjFunc(const GameCharacter3& gc)
{
printf("%s\n", __FUNCTION__);
}
void dogObjFunc(const GameCharacter3& gc)
{
printf("%s\n", __FUNCTION__);
}
class GameCharacter3
{
public:
//typedef void (*HealthCalcFunc)(const GameCharacter3&);
typedef std::tr1::function<void (const GameCharacter3&)> ObjFunc;
explicit GameCharacter3(ObjFunc hcf = defaultObjFunc)
: m_healthFunc(hcf)
{}
void healthValue() const
{
m_healthFunc(*this);
}
private:
ObjFunc m_healthFunc;
};
class GameCharacter4;
class CHealthCalc
{
public:
virtual int calc(const GameCharacter4& gc) const
{
printf("CHealthCalc::%s\n", __FUNCTION__);
}
};
CHealthCalc defaultCHealthCalc;
class GameCharacter4
{
public:
explicit GameCharacter4(CHealthCalc* pChc = &defaultCHealthCalc)
: pHealthCalc(pChc)
{}
int healthValue() const
{
return pHealthCalc->calc(*this);
}
private:
CHealthCalc* pHealthCalc;
};
int main(int argc, char* argv[])
{
//NVI手法
GameCharacter* p = new Player;
p->healthValue();
delete p;
//将virtual函数替换为“函数指针成员变量”
GameCharacter2 gc1;
gc1.healthValue();
GameCharacter2 gc2(dogHealthCalc);
gc2.healthValue();
//以tr1::function成员变量替换virtual函数
GameCharacter3 go1;
go1.healthValue();
GameCharacter3 go2(dogObjFunc);
go2.healthValue();
//将一个继承体系内的virtual函数替换为另一个继承体系内的virtual函数
GameCharacter4 gcl1;
gcl1.healthValue();
return 0;
}
36,绝不重新定义继承而来的non-virtual函数
37,绝对不要重新定义继承而来的参数值
39,明智而审慎的使用private继承
40,明智而审慎的时候多重继承
41,了解隐式接口和编译器多态
面向对象编程世界总是以显式接口和运行期多态来解决问题,而模板元编程这相反。
发生在编译期间的template具现化成为编译期多态。
class和template都支持接口和多态。
对class而言接口是显示的,以函数签名为中心,多态则是通过virtual函数发生在运行期。
对template参数而言,接口是隐式的,奠基于有效表达式,多态则是通过template具现化和函数重载解析发生于编译期。
42,了解typename的双重意义
声明template参数时,前缀关键字class和typename是完全一样的。
请使用typename标识嵌套从属类型名称,但不得在基类列或成员初始列内使用。
43,学习处理模板化基类内的名称
模板特化即针对某种类型的进行特殊处理,不再通过通用模板编译代码。
派生类模板调用基类模板的成员函数时应告诉编译期怎样调用,可通过this或using 指明调用基类函数
44,将与参数无关的代码抽离
这些代码将导致template具现化所带来的代码膨胀
45,运用成员函数模板接受所有兼容类型
如何在模板内定义成员函数模板,在定义了泛化构造和赋值操作函数之后,仍应显示定义一般式。
46,需要类型转换时请为模板定义非成员函数
请参看第24点
#include <iostream>
template<typename T>
class Rational;
template<typename T>
const Rational<T> doMultiply(const Rational<T>& , const Rational<T>&);
template<typename T>
class Rational
{
public:
Rational<T>(T ca, T cb)
: a(ca), b(cb)
{}
void print()
{
std::cout << a << "," << b << std::endl;
}
friend const Rational<T> operator*(const Rational<T>& lhs, const Rational<T>& rhs)
{
return doMultiply(lhs, rhs);
}
public:
T a;
T b;
};
template<typename T>
const Rational<T> doMultiply(const Rational<T>& lhs, const Rational<T>& rhs)
{
return Rational<T>(lhs.a + lhs.a, lhs.b + rhs.b);
}
int main(int argc, char* argv[])
{
Rational<int> r1(1, 2);
Rational<int> r2(3, 4);
Rational<int> r3 = r1 * r2;
Rational<double> r4(10.0, 20.0);
Rational<double> r5 = r4 * r4;
r1.print();
r2.print();
r3.print();
r4.print();
r5.print();
return 0;
}
47,请使用traits classes变现类型信息
48,认识template模板元编程
#include <stdio.h>
template<unsigned n>
struct ftor
{
enum{nValue = n * ftor<n-1>::nValue};
};
template<>
struct ftor<0>
{
enum{nValue = 1};
};
int main(int argc, char* argv[])
{
printf("%d\n", ftor<5>::nValue);
return 0;
}
49,了解new-handler的行为
std::set_new_handler
std::get_new_handler
#include <iostream>
#include <new>
#include <cstdio>
#include <cstdlib>
namespace std
{
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
};
void OutofMemory()
{
char szBuff[128] = "";
snprintf(szBuff, sizeof(szBuff), "%s:out of memory!!!", __FUNCTION__);
std::cout << szBuff << std::endl;
std::abort();
}
//RAII class
class NewHolder
{
public:
explicit NewHolder(std::new_handler nh)
: handler(nh)
{}
~NewHolder()
{
std::set_new_handler(handler);
}
private:
std::new_handler handler;
NewHolder(const NewHolder&);
NewHolder& operator=(const NewHolder&);
};
//New Handler Support class
template<typename T>
class NewSupport
{
public:
static std::new_handler set_new_handler(std::new_handler p) throw();
static void* operator new(std::size_t size) throw(std::bad_alloc);
static void* operator new[](std::size_t size) throw(std::bad_alloc);
private:
static std::new_handler currentHandler;
};
template<typename T>
std::new_handler NewSupport<T>::set_new_handler(std::new_handler p) throw()
{
std::new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
template<typename T>
void* NewSupport<T>::operator new(std::size_t size) throw(std::bad_alloc)
{
NewHolder h(std::set_new_handler(currentHandler));
return ::operator new(size);
}
template<typename T>
void* NewSupport<T>::operator new[](std::size_t size) throw(std::bad_alloc)
{
NewHolder h(std::set_new_handler(currentHandler));
return ::operator new [](size);
}
template<typename T>
std::new_handler NewSupport<T>::currentHandler = NULL;
class Test : public NewSupport<Test>
{
public:
Test()
{
//p = new int[10000000000L];
}
~Test()
{
//delete p;
//p = NULL;
}
void print()
{
std::cout << "Test Class print()!!!" << std::endl;
}
private:
int* p;
};
int main(int argc, char* argv[])
{
// 设置全局的handler
// std::set_new_handler(OutofMemory);
// int* p = new int[10000000000L];
// delete p;
// 设置class Test的handler
// Test::set_new_handler(OutofMemory);
// Test* p = new Test[10000000000L];
// 看看在哪里handler了
// Test::set_new_handler(OutofMemory);
// Test* p = new Test;
// p->print();
// delete p;
return 0;
}
50,定制new和delete
A)为了效能
B)为了收集使用上的统计数据
C)为了检测运用错误
D)为了收集动态分配内存之使用统计信息
E)为了增加分配和归还速度
F)为了降低缺省内存管理器带来的空间额外开销
G)为了弥补缺省分配器中的非最佳位对齐
H)为了将对象成簇集中
51,编写new和delete时需固守常规
52,写了placement new也要写placement delete
定制版的new/delete要对应,同时主要不要掩盖正常版本
53,不要忽略编译器警告
严肃对待编译期发出来的抱怨,也不能过分依赖编译,每个编译器都有不同。
54,让自己熟悉包括TR1在内的标准程序库
55,让自己熟悉boost
来源:oschina
链接:https://my.oschina.net/u/70773/blog/327308