文章目录
系列文章:
C++的35个技巧阅读笔记(一)
C++的35个技巧阅读笔记(二)
21.通过重载避免隐式类型转换
利用重载技术时候需要注意,每一个重载的operator必须带有一个“用户定制类型”的参数。不要忘了80-20原则,增加一大堆重载函数不一定是件好事,除非使用重载函数后,程序的整体效率获得重大的改善。
查看条款19
22.考虑以操作符复合形式(op =)取代其单独形式(op)
要确保操作符的复合形式(operator+=)和其独身形式(operator+)之间的自然关系能
够存在,一个好方法就是以前者(+=)为基础实现后者(+)
。
class Rational {
public:
Rational& operator+=(const Rational& rhs);
Rational& operator-=(const Rational& rhs);
};
//利用operator+=实现
const Rational operator+(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs) += rhs;
}
//或者使用模板实现
template<class T>
const T operator+(const T& lhs, const T& rhs);
{
return T(lhs) += rhs;
}
1、一般而言,复合操作符比其对应的独身版本效率高。因为独身版本通常必须返回一个新对象,我们必须负担临时对象的构造和析构成本。
2、如果同时提供某个操作符的复合形式和独身形式,便允许你的客户在有效率与便利性之间做取舍。
23.考虑改变程序库
不同的程序库即使提供相似的机能,也往往表现出不同性能取舍策略,所以一旦你找出程序的瓶颈,你应该考虑是否有可能因为该用另一个程序库而移除这些瓶颈。
24.了解 virtual functions(虚函数)、multiple inheritance(多继承)、virtual base classes(虚基类)、runtime type identification(运行时类型识别)的代价
- 1、
vtbl(虚函数表)
通常是一个由“函数指针”架构而成的数组,某些编译器会以链表取代数组,但基本策略相同。尽量避免将虚函数声明为inline,方法一是探测式,类的虚函数一般产生于内含其第一个non-inline,non-pure虚函数定义式的目标文件中。 - 2、多继承时,单个对象有多个
vptrs(虚表指针)
,会增加对象体积大小。 - 3、RTTI能让我们在运行时找到对象和类的有关信息,所以肯定有某个地方存储了这些信息让我们查询,这些信息被存储在类型为type_info的对象里。通常,RTTI被设计为在类的vbtl上实现。
性质 对象大小增加 Class数据量增加 Inlining几率降低 虚函数 是 是 是 多重继承 是 是 否 虚拟基类 常常 有时候 否 运行时类型识别 否 是 否
25.将constructor和non-member functions虚化
所谓virtual constructor是某种函数,视其获得的输入,课产生不同类型的对象。virtual constructor在许多情况下有用,其中之一就是从磁盘读取对象信息。
26.限制某个class所能产生的对象数量
- 1、阻止某个class产生对象最简单的方法是将其constructor 声明为private。方法一:让打印机成为一个function static,如下所示:
namespace PrintingStuff{
class Printer
{
public:
submitJob(const PrintJob& job);
void reset();
void performSelfTest();
...
friend Printer& thePrinter();// 友元
private:
Printer();
Printer(const Printer& shs);
...
};
Printer& thePrinter()
{
static Printer p; //形成唯一一个Printer对象,是函数中的static 对象而非class中的static对象。
return p;
}
}
//使用了namespace之后我们可以这样调用
PrintingStuff::thePrinter().reset();
PrintingStuff::thePrinter().submitJob(buffer);
//使用 using PrintingStuff::thePrinter;
thePrinter().reset();
thePrinter().submitJob(buffer);
注意:“class拥有一个static 对象”的意思是:即使从未被使用到,它也会被构造(及析构)。
“函数拥有一个static对象”的意思是:(函数里面,局部static变量)此对象在函数第一次被调用时才产生。(然而你必须在函数每次调用时检查对象是否需要诞生)
- 2、利用
numObjects
来追踪记录目前存在多少个Printer对象。这个数值在constructor中累加,并在destructor中递减。如果外界企图构造太多Printer对象,我们就抛出一个类型为TooManyObjects的exception。
//将对象计数和伪构造函数结合
class Printer
{
public:
class TooManyObjects();
//伪构造函数
static Printer * makePrinter();
static Printer * makePrinter(const Printer& rhs);
~Printer();
void reset();
...
private:
static size_t numObjects;
static const size_t maxObjects = 10; //设置允许对象个数
//若不支持使用enum{ maxObjects = 10 };或者像numObjects一样
Printer();
Printer(const Printer& rhs); //若只允许一个时候,不要定义此函数,因为我们决不允许复制行为
};
size_t Printer::numObjects = 0;
const size_t Printer::maxObjects;
Printer::Printer()
{
if(numObject >= maxObjects){ //可以自定义限制对象个数。
throw TooManyObjects();
}
proceed with normal construction here;
++numObjects;
}
Printer::Printer(const Printer& rhs)
{
if(numObject >= maxObjects){ //可以自定义限制对象个数。
throw TooManyObjects();
}
proceed with normal construction here;
++numObjects;
}
Printer * Printer::makePrinter()
{ return new Printer; }
Printer * Printer::makePrinter(const Printer& rhs)
{ return new Printer(rhs); }
Printer::~Printer()
{
perform normal destruction here;
--numObject;
}
Printer p1; //错误!default ctor 是private;
Printer *p2 = Printer::makePrinter(); //没问题,间接调用 default ctor;
Printer p3 = *p2; //错误!copy ctor是private
p2->reset();
...
delete p2; //避免资源泄漏,如果p2是个auto_ptr此动作不需要
仅仅使用“对象计数”这种方式带来的问题是:继承和组合时候,numObjects可能并不能正确表示。Printer对象可于三种不同状态下生存:(1)它自己(2)派生物的“base class成分”(3)内嵌(组合)于较大对象之中。
“带有private constructors 的 class 不得被继承”这一事实导致“禁止派生”的一般性体制。
一个用来计算对象个数的Base Class
template<class BeingCounted>
class Counted
{
public:
class TooManyObjects {};
static int objectCount() { return numObjects; }
protected: //设计作为base classes
Counted();
Counted(const Count& rhs);
~Counted() { --numObjects; }
private:
static int numObjects;
static const size_t maxObjects;
void init(); //用来避免ctor码重复出现
};
template<class BeingCounted>
Counted<BeingCounted>::Counted(){ init(); }
template<class BeingCounted>
Counted<BeingCounted>::Counted(const Counted<BeingCounted>&){ init(); }
template<class BeingCounted>
void Counted<BeingCounted>::init()
{
if(numObjects >= maxObjects) throw TooManyObjects();
++numObjects;
}
template<class BeingCounted>
int Counted<BeingCounted>::numObjects; //定义numObject,并自动初始化为0;const maxObject,留给用户自己定义。
//Printer class 运用Counted template:
class Printer : private Counted<Printer> //利用Counted template 追踪目前有多少对象的实现细节最好保持private。
{
public:
static Printer * makePrinter();
static Printer * makePrinter(const Printer& rhs);
~Printer();
using Counted<Printer>::objectCount; //让此函数对于Printer的用户而言成为public的,用户就可以知道有多少个对象存在
void submitJob(const PrinterJob& job);
void reset();
...
using Counted<Printer>::objectCount;
using Counted<Printer>::TooManyObject;
private:
Printer();
Printer(const Printer& rhs);
};
27.要求(或禁止)对象产生于heap之中
- 1、要求对象产生于heap之中,比较好的方法是让destructor成为private,而constructor仍然是public,利用伪destructor函数来调用真正的destructor。但是妨碍了继承和内含,但是都可以解决:
class UPNumber
{
public:
UPNumber();
UPNmumber(int initValue);
UPNumber(double initValue);
UPNumber(const UPNumber& rhs);
//伪destructor
void destroy() const { delete this; }
...
private:
~UPNumber(); //注意:dtor位于private 区内
};
UPNumber n; //错误!(虽然合法,但当n的dtor稍后被隐式调用,就不合法了)
UPNumber *p = new UPNumber; //良好
...
delete p; //错误!企图调用private destructor
p->destroy(); //良好
要继承时候,可以将private的destructor声明了protected即可。
要内含的话,可以修改为“内含一个指针,指向UPNumber”。
- 2.判断某个对象是否位于Heap内?
static对象(涵盖声明为static的对象、global scope 和namespace scope内的对象)在什么位置?——视系统而定,在许多系统中,如下所示:
Stack向下成长——高地址
Heap向上成长
Static Objects——底地址
判断指针是否以oeprator new分配出来?——如下
class HeapTracked
{
public:
class MissingAddress{}; //
virtual ~HeapTracked() = 0; //纯虚函数使得HeapTracked成为抽象类
static void *operator new(size_t size);
static void operator delete(void *ptr);
bool isOnHeap() const;
private:
typedef const void* RawAddress;
static list<RawAddress> addresses;
};
list<RawAddress>HeapTracked::addresses;
HeapTracked::~HeapTracked(){} //HeapTracked为抽象类,但是destructor仍然必须有定义,所以提供一个空定义
void * HeapTracked::operator new(size_t size)
{
void *memPtr = ::operator new(size);
addresses.push_front(memPtr);
return memPtr;
}
void HeapTracked::operator delete(void *ptr)
{
list<RawAddress>::iterator ot = find(addresses.begin(), addresses.end(), ptr);
if(it !=addresses.end()){ //如果找到符合条件的元素,移除,并释放内存
addresses.erase(it); //否则表示ptr 不是operator new 所分配,于是抛出一个exception。
::operator delete(ptr);
}else{
throw MissingAddress();
}
}
bool HeapTracked::isOnHeap() const
{ //取得一个指针,指向*this 所占内存的起始处;
const void *rawAddress = dynamic_cast<const void*>(this);
list<RawAddress>::iterator it = find(address.begin(), addresses.end(), rawAddress);
return it != addresses.end();
}
- 3.禁止对象产生于heap之中
(1)对象被直接实例化于heap之中
(2)对象被实例化为derived class object内的“base class 成分”
(3)对象被内嵌于其他对象之中
class UPNumber
{
private:
static void *operator new(size_t size);
static void operator delete(void *ptr);
...
};
UPNumber n1; //可以
static UPNumber n2; //也可以
UPNumber *p = new UPNumber; //错误,企图调用private operator new。
注意:1)因为对象总是调用operator new,而后者我们可以自行声明。因此可以将operator new声明为private应该足够了。2)将operator new 声明为private,往往也会妨碍UPNumber被实例化为heap-base derived class objects的“base class成份""