Effective C++ 第三版 摘要

本文总结了C++高效编程的55条基本原则,涵盖了语言特性理解、资源管理、设计与声明、实现细节、继承与面向对象编程、模板与泛型编程、定制new和delete操作符等方面,帮助开发者掌握最佳实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、让自己习惯C++

01:视C++为一个语言联邦
1)多重泛型编程语言:支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式。
2)主要的次语言:以C为基础、面向对象c++、泛型编程、STL。
3)C++高效编程守则视状况而变化,取决于你使用C++的哪一个部分。

02:尽量以const,enum,enum,inline替换#define
1)以常量替代宏,编译器可以看到该名称,该名称会进入记号表内。
2)宏可能导致目标代码出现多份替换,使用常量可以减少目标代码。
3)对于单纯常量,最好以const对象或enums替换#define。
4)对于形似函数的宏,最好改用inline函数替换#define。

03:尽可能使用const
1)令函数返回一个常量值,可以避免返回值被修改,提高安全性。
2)const可以用来实现函数重载。

04:确定对象使用前已先被初始化
1)为内置对象进行手工初始化。
2)构造函数最好使用成员初始化列表,而不要再构造函数内使用赋值操作。初始化列表的成员变量,其排列次序最好和类中的声明次序一致。

2、构造/析构/赋值运算

05:了解C++默默编写并调用哪些函数

1)编译器可以暗自为class创建默认构造函数、拷贝构造函数、拷贝赋值运算符,以及析构函数。

06:若不想使用编译器自动生成的函数,就该明确拒绝
1)为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。

07:为多态基类声明virtual析构函数
1)带多态性质的基类应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
2)类的设计目的如果不是作为基类使用,或不是为了具备多态性,就不该声明virtual析构函数。

08:别让异常逃离析构函数
1)析构函数绝对不要抛出异常。
2)应该在普通函数中对某个操作函数运行期间抛出的异常做出反应。

09:绝不在构造和析构过程中调用virtual函数

10:令拷贝赋值运算符返回一个*this引用

11:在拷贝赋值运算符中处理“自我赋值”
1)确保当对象自我赋值时有良好行为。其中技术包括证同测试(会使代码变大,导入新的控制流)、精心周到的语句顺序(注意资源的释放过程),以及copy-and-swap。

12:赋值对象时勿忘其每一个成分

3、资源管理

13:以对象管理资源

1)使用share_ptr和auto_ptr

14:在资源管理类中小心copy行为
1)复制RAII(资源取得时机便是初始化时机)对象必须一并复制它所管理的资源。
2)RAII类的拷贝行为是:抑制拷贝、施行引用计数法

15:在资源管理类中提供对原始资源的访问
1)每一个RAII类应该提供一个“取得其所管理之资源”的办法。
2)对原始资源的访问可能经由显示转换或隐式转换,显示转换比较安全,隐式转换对客户比较方便。

16:成对使用new和delete时要采取相同形式

17:以独立语句将newed对象置入智能指针
1)由于参数的调用顺序是未定义的,所以有可能newed对象还未置入智能指针时,编译器在调用其他函数时抛出异常,导致资源泄露。

4、设计与声明

18:让接口容易被正确使用,不易被误用

1)“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容。
2)“阻止误用”的办法包括建立新类型、限制类型上的操作、束缚对象值,以及消除客户的资源管理责任。
3)share_ptr支持定制型删除器,可以防范DLL问题(对象在DLL中被new,在另一个DLL中被delete),可被用来自动解除互斥锁。

19:设计class犹如设计type

20:以引用参数替换值参数
1)该规则不适用于内置类型,以及STL的迭代器和函数对象。

21:必须返回对象时,别妄想返回其引用
1)不要返回局部对象的引用或地址。
2)不要返回堆对象的引用。
3)不要返回局部静态对象的引用或地址,非线程安全。

22:将成员变量声明为private

23:以非成员函数或非友元函数替换成员函数
1)越多函数可以访问成员变量,数据的封装性就越低。

24:若所有参数皆需要类型转换,请为此采用非成员函数

25:考虑写出一个不抛出异常的swap函数
template<typename T>
class Widget
{
WidgetImpl *widget_impl_;
public:
void swap(Widget<T> &other)
{
using std::swap;
swap(widget_impl_, other.widget_impl_);
}
};
template<typename T>
void swap(Widget<T> &a, Widget<T> &b)
{
a.swap(b);
}

Widget<int> w1, w2;
swap(w1, w2);

5、实现

26:尽可能延后变量定义式的出现时间

 
27:尽量少做转型动作
1)const_cast通常被用来将对象的常量性转除,dynamic_cast主要用来执行“安全向下转型”,static_cast用来强迫隐式转换。
2)在注重效率的代码中避免使用dynamic_cast。
3)如果转型是必要的,试着将它隐藏于某个函数背后。
4)宁可使用C++风格转型,不要使用旧式转型。

28:避免返回handles指向对象内部成分
1)避免返回引用、指针、迭代器指向对象内部,可增加封装性。

29:为“异常安全”而努力是值得的
class PrettyMenu
{
...
std::tr1::shared_ptr<Image> bgImage;
...
};
void PrettyMenu::changeBackground(std::instream &imgSrc)
{
Lock ml(&mutex);
bgImage.reset(new Image(imgSrc));
++imageChanges;
}
1)异常安全函数即使发生异常也不会泄露资源或允许任何数据结构败坏。
class PMImpl
{
std::tr1::shared_ptr<Image> bgImage;
int imageChanges;
};
class PrettyMenu
{
...
private:
std::tr1::shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::instream &imgSrc)
{
using std::swap;
Lock ml(&mutex);
std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));
pNew->gbImage.reset(new Image(imgSrc));
++pNew->imageChanges;
swap(pImpl, pNew);
}
2)“强烈保证”往往能够以copy_and_swap实现出来,但并非对所有函数都可实现或具备实现意义。

30:透彻了解inlining的里里外外

31:将文件间的编译依存关系降至最低
1)如果使用引用或指针可以完成任务,就不要使用对象。
2)尽量以class声明式替换class定义式。
3)为声明和定义提供不同的头文件
4)使用Interface classes解除接口和实现之间的耦合关系
//person.h
#include <string>
#include <memory>
using std::string;
class PersonImpl
class Date;
class Address;
class Person
{
public:
Person(const string &name, const Date &birthday, const Address &addr);
  string name()const;
  string birthDate()const;
  string address()const;
  ...
private:
std::tr1::shared_ptr<PersonImpl> pImpl;
};

//Person.cpp
#include "Person.h"
#include "PersonImpl.h"

Person::Person(const string &name, const Date &birthday, const Address &addr)
:pImpl(new PersonImpl(name, birthday, addr))
{
}

std::string Person::name() const
{
return pImpl->name();
}
5)使用Handle classes解除接口和实现之间的耦合关系
//Person.h
class Person
{
public:
virtual ~Person();
  string name()const = 0;
  string birthDate()const = 0;
  string address()const = 0;
  static std::tr1::shared_ptr<Person> create(const string &name, const Date &birthday, const Address &addr);
};

class RealPerson: public Person
{
public:
RealPerson(const string &name, const Date &birthday, const Address &addr)
: theName(name), theBirthDate(birthday), theAddress(addr)
{}
virtual ~RealPerson() {}
string name() const;
string birthDate() const;
string address() const;
private:
string theName;
Date theBirthDate;
Address theAddress;
}

std::str1::shared_ptr<Person> Person::create(const string &name, const Date &birthday, const Address &addr)
{
return std::str1::shared_ptr<Person>(new RealPerson(name, birthday, addr));
}

6、继承与面向对象

32:确定你的public继承塑模出is-a关系


33:避免遮掩继承而来的名称
class Base
{
public:
virtual void mf1() = 0;
virtual void mf1(int);
virtual void mf2() = 0;
void mf3() = 0;
void mf3(double) = 0;
};
class Derived: public Base
{
public:
using Base::mf1();
using Base::mf3();
virtual void mf1(); //遮掩了Base的同名函数
{
Base::mf1(); //使mf1()成为转交函数
}
void mf3(); //遮掩了Base的同名函数
void mf4();
}

34:区分接口继承和实现继承

35:考虑虚函数以外的其他选择
1)NVI手法
class GameCharacter
{
public:
int healthValue() const
{
... //事前工作
int retVal = doHealthValue();
... //事后工作
return retVal;
}
private:
virtual int doHealthValue() const
{ ... }
};
2)古典的strategy模式
class GameCharacter;
class HealthCalcFunc
{
virtual int calc(const GameCharacter &gc) const
{ ... }
};
HealthCalcFunc defaulHealthCalc;
class GameCharacter
{
public:
explicit GameCharacter(HealthCalcFunc *phcf = &defaultHealthCalc)
: pHealthCalc(phcf)
{}
int healtValue() const
{
return pHealthCalc->calc(*this);
}
...
private:
HealthCalcFunc *pHealthCalc;
};

36:绝不重新定义继承而来的非虚函数

37:绝不重新定义继承而来的缺省参数值

38:通过复合塑模出has-a或“根据某物实现出”

39:明智而审慎地使用private继承
1)尽可能使用复合,必要时才使用private继承。
class Widget
{
private:
class WidgetTimer: public Timer
{
public:
virtual void onTick() const;
...
};
WidgetTimer timer;
}
//Widget不是一个Timer
//Widget的子类无法修改onTick函数
//移出内部类,降低耦合
2)private继承意味着is-implemented-in-terms of。它通常比复合的级别低。但是当派生类需要访问基类的protected成员,或需要重新定义继承而来的虚函数是,这么设计是合理的。
3)private继承可以造成empty base最优化。

40:明智而审慎地使用多重继承
1)虚继承会增加大小、速度、初始化复杂度。如果虚基类不带任何数据,将是最具实用价值的情况。
2)多重继承的正当用途情况有:public继承某个接口类,private继承某个协助实现的类。

7、模板与泛型编程

41:了解隐式接口和编译期多态

1)classes和templates都支持接口和多态。
2)对classes而言接口是显式的,以函数签名为中心。多态则是通过虚函数发生于运行期。
3)对template参数而言,接口是隐式的,奠基于有效表达式。多态则是通过template具现化和函数重载解析发生于编译期。

42:了解typename的双重意义
1)声明template参数时,class和typename可互换。
2)使用typename标识嵌套从属类型名称,不得在基类列或成员初值列内以它作为基类修饰符。

43:学习处理模板化基类内的名称
继承模板化基类的名称不能像继承一样使用,应该通过this->名字修饰、using基类<T>::名字、基类<T>::名字

44:将与参数无关的代码抽离
1)因非类型模板参数而造成的代码膨胀,可以以函数参数或类成员变量替换模板参数。
2)因类型参数而造成的代码膨胀,可以让带有完全相同二进制表述的具体类型共享实现码。

45:运用成员函数模板接受所有兼容类型

46:需要类型转换时请为模板定义非成员函数
1)当模板化的类所提供的“与模板相关的”函数支持“所有参数之隐式类型转换”时,将这些函数定义为友元函数。

47:请使用traits类表现类型信息

48:认识模板元编程
1)可将工作由运行期移往编译期。
2)可用来避免生成对某些特殊类型并不适合的代码。

8、定制new和delete

49:了解new-handler的行为

1)set_new_handler可以指定一个函数,在内存分配无法获得满足时被调用

50:了解new和delete的合理替换时机
1)改善效能、对堆运用错误进行调试、收集堆使用信息

51:编写new和delete时需固守常规
1)操作符new应内含一个无穷循环,并在其中尝试分配内存,无法满足内存需求时,调用new-handler,有能力处理0bytes申请或超额申请。
2)操作符delete应在收到null指针时不做任何事,处理超额申请。

52:写了placement new也要写placement delete

9、杂项讨论

53:不要轻忽编译器的警告


54:熟悉包括TR1在内的标准程序库

55:熟悉Boost
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值