C++的35个技巧阅读笔记(三)


系列文章:
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成份""

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值