Accustoming Yourself to C++
(1) C
(2)Object-Oriented C++
(3) Template C++
(4)STL
【使用常量替换宏定义】
const double AspectRatio = 1.653;
The enum hack
class GamePlayer {
private:
enum{NumTurns = 5};
int scores[NumTurns]
}
【使用inline函数替换宏定义】
对此《C++ Primer Plus》有详细解释。
char greeting[] = "Hello";
char* p = greeting; // non-const pointer, no-const data.
const char* p = greeting; // non-const pointer, const data.
char* const p = greeting; // const pointer, non-const data.
const char* const p = greeting; // const pointer, const data.
如果const出现在 * 左边,则被指向的Object是常量。
如果const出现在 * 右边,则指针本身是常量。
【const 修饰函数参数】
void f1(const Widget* pw);
void f2(Widget const * pw);
f1和f2的参数是等价的。都是常量指针。
【const 修饰函数返回值】
防止如下事情出现
if(a * b = c) {
}
【const修饰成员函数】
class TextBlock {
public:
// operator [] for const object
const char& operator[](std::size_t position) const {
return text[position];
}
// operator [] for non-const object.
char& operator[](std::size_t position) {
return text[position];
}
}
TextBlock tb("hello");
std::cout << tb[0]; // call non-const []
const TextBlock ctb("World");
std::cout << ctb[0]; // call const []
cout << tb[0]; // ok. read for non-const TextBlock.
tb[0] = 'x'; // ok. write for non-const TextBlock.
cout << ctb[0]; // ok. read for const TextBlock.
ctb[0] = 'x'; // error! write for const TextBlock.
【在const和non-const成员函数中避免重复】
class TextBlock {
public:
// operator [] for const object
const char& operator[](std::size_t position) const {
// do sth.
return text[position];
}
// operator [] for non-const object.
char& operator[](std::size_t position) {
return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
}
}
static_cast:为(*this)添加const,指明调用的是const版本的函数,避免递归不退出。
const_cast: 为返回值移除const。
【编译器强制实施bitwise constness,但是程序员在编程的时候应该使用的是conceptual constness】
【推荐使用成员初值列(member initializing list)的方式实现构造函数】
ABEntry::ABEntry(string& name, string& address, list<PhoneNumber> phones)
:theName(name), // 这些都是在初始化
theAddress(address),
thePhones(phones),
numTimesConsulted(0) {
}
【C++对“定义于不同编译单元内的non-local static对象”的初始化次序没有明确定义】
C++保证,函数内的local static对象会在“该函数调用期间”“首次遇上该对象之定义式”时被初始化。
class FileSystem {...};
FileSystem& tfs() {
static FileSystem fs;
return fs;
}
class Directory {...};
Directory::Directory(params) {
...
std::size_t disks = tfs().numDisks();
...
}
Directory& tempDir() {
static Directory td;
return td;
}
class Empty {}
等价于
class Empty {
public:
Empty() {} // default构造函数
Empty(const Empty& rhs) {} // copy构造函数
~Empty() {} // 析构函数
Empty& operator=(const Empty& rhs) {} // copy assignment操作符
}
【将copy构造函数和copy assignment声明为private】
class HomeForSale {
public:
private:
HomeForSale(const HomeForSale&);
HomeForSale& operator=(const HomeForSale&);
}
正常情况下,试图copy HomeForSale object,编译报错。如果不慎在member函数或者friend函数中进行拷贝,会在链接期报错。
【设计不可拷贝的基类】
class UnCopyable {
protected:
UnCopyable() {} // 允许derived对象构造和析构
~UnCopyable() {}
private:
UnCopyable(const UnCopyable&); // 阻止copy
UnCopyable& operator=(const UnCopyable&);
}
class HomeForSale : private UnCopyable {
}
任何class只要带有virtual函数都几乎可以确定也应该有一个virtual的析构函数。 防止Object的局部销毁。
当一个class不打算作为一个base class,就不应该将析构函数声明为virtual的。防止将对象的size变大。
经验: 只有当class内至少有一个virtual函数的时候,才为这个class声明virtual 析构函数。
Pure virtual 函数导致抽象类。为希望成为抽象类的class声明一个pure virtual的析构函数。必须为纯虚的析构函数提供定义,否则连接过程会出现问题。
class DbConn {
public:
void close() { // 客户可以手动close数据库连接
db.close();
closed = true;
}
~DbConn() {
if (!closed) {
try {
db.close(); // 兜底机制,关闭数据库连接。
} catch(...) {
// process exception.
... // 如果关闭动作失败,记录下来并结束程序或者吞下异常。
}
}
}
private:
DbConnection db;
bool closed; // 记录是否手动关闭异常了。
}
Base class构造期间virtual函数不会下降到derived class。“在base class构造期间,virtual函数不是virtual函数”。
解决方案如下:
class Transaction {
public:
explict Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo); // non-virtual function.
};
Transaction::Transaction(const std::string& logInfo) {
...
logTransaction(logInfo); // non-virtual调用
}
class BuyTransaction: public Transaction {
public:
BuyTransaction(parameters)
: Transaction(createLogString(parameters)) { // 将log信息传递给base class构造函数。
}
private:
static std::string createLogString(parameters);
}
因为有人喜欢使用连锁形式的赋值。
int x,y,z;
x = y = z = 15; // 连锁形式赋值
class Widget {
public:
Widget& operator=(const Widget& rhs) {
...
return *this;
}
}
class Bitmap {};
class Widget {
private:
Bitmap* pb;
}
【证同测试(identity test)】
Widget& Widget::operator=(const Widget& rhs) {
if (this == &rhs) {
return *this;
}
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
【实现“异常安全性”】
实现了“异常安全性”往往自动可以达到“自我赋值安全”。
Widget& Widget::operator=(const Widget& rhs) {
Bitmap* pOrigin = pb;
pb = new Bitmap(*rhs.pb);
delete pOrigin;
return *this;
}
copying函数指拷贝构造函数和operator=。
当编写一个copying函数的时候,请确保:(1)复制所有的local成员变量。(2)调用所有base classes内的适当的copying函数。
如果copy构造函数和operator=的代码几乎相同,请抽取私有公共方法。
void f() {
std::shared_ptr<Investment> pInv(createInvestment());
}
获得资源之后立即放到管理对象(managing object)内。“资源取得的时机便是管理对象的初始化时机”(Resource Acquisition is Initialization,RAII)。
管理对象(managing object)运用析构函数确保资源被释放。
class Lock {
public:
explicit Lock(Mutex* pm): mutextPtr(pm) {
lock(mutextPtr);
}
~Lock() {
unlock(mutextPtr);
}
private:
Mutex* mutexPtr;
}
Mutex m;
{
Lock m1(&m); // 锁定互斥器
}
Lock m2(m1); // 复制会发生什么事呢?
【禁止复制】
class Lock : private Uncopyable {
}
【使用引用计数法】
class Lock {
public:
explicit Lock(Mutext* pm)
:mutextPtr(pm, unlock) { // 指定unlock为删除器
lock(mutexPtr.get());
}
private:
std::tr1::shared_ptr<Mutex> mutexPtr;
}
【复制底部资源】
deep copying
【转移底部资源的拥有权】
资源的拥有权从被复制物转移到了目标物。
【尽量不要对数组形式做typedef动作】
typedef std::string AddressLine[4]; // 每个人的地址有4行,每行是一个string。
std::string* pal = new AddressLines; // new AddressLines返回的是string*,就像new string[4]一样
delete pal; // 行为未有定义
delete[] pal // 正确的
使用STL中的集合类就可以达到同样的效果。
请记住:
int priority();
void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority); // 可能出现资源泄漏
c++编译器以什么样的顺序完成参数的准备是不确定的。
如果按照以下顺序,就有可能发生问题。
(1)new widget
(2)调用priority
(3)调用shared_ptr的构造函数。
如果priority函数的调用发生了异常,new Widget的指针将会遗失,并未置入智能指针中。
解决办法就是将语句分开
std::tr1::shared_ptr<Widget> pw(new Widget)
processWidget(pw, priority);
【类型系统】
class Date {
public:
Date(int month, int day, int year);
};
Date d(30, 3, 1995); // 应该是3月30号
Date d(2, 30, 1995); // 2月哪里有30号呀
struct Day {
explicit Day(int d):val(d) {}
int val;
}
class Date {
public:
Date(const Month& m, const Day& d, const Year& y);
};
Date d(30, 3, 1995); // 不正确的参数类型
Date d(Day(30), Month(3), Year(1995)); // 不正确的参数类型
Date d(Month(3), Day(30), Year(1995)); // 正确
class Month {
public:
static Month Jan() {return Month(1);} // 函数返回有效月份
static Month Feb() {return Month(2);}
...
static Month Dec() {return Month(12);}
private:
explicit Month(int m); // 阻止生成新的月份
};
Date d(Month::Mar(), Day(30), Year(1995));
【除非有好理由,否则应该令你的types行为与内置的types一致】
【使用智能指针作为返回值】
std::tr1:shared_ptr<Investment> createInvestment();
std::tr1::shared_ptr<Investment> createInvestment() {
std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0), getRidOfInvestment);
retVal = ...; // 令retVal指向正确的对象。
return retVal;
}
设计规范:
【新type的对象应该如何被创建和销毁?】
影响到构造函数和析构函数,内存分配释放函数。
【对象的初始化和对象的赋值有什么差别?】
【新type的对象如果被passed by value,意味着什么?】
copy构造函数用来定义一个type的pass-by-value应该如何实现。
【什么是新type的合法值?】
class必须维护的约束条件;
成员函数(特别是构造函数、赋值操作符和setter)进行的错误检查;
函数抛出的异常,以及函数异常明细(exception specifications)。
【新type要配合某个继承图系(inheritance graph)吗?】
如果是继承某个父类的,会受到父类的设计约束,特别是virtual或者non-virtual的影响。
如果是允许派生的,这回影响类内声明的函数,尤其是析构函数——因为要考虑析构函数是否被设计成virtual的。
【新type需要什么样的转换?】