More Effective C++ 学习笔记(4)

效率

一些漠视的简单规则:

  • 循环可以手动展开
  • 乘法可以用移位运算代替
  • 减少非必要的对象产生和销毁

条款16:谨记8-2法则

一个程序80%的资源用在20%的代码身上,软件整体性能几乎总是由构成要素的一小部分决定

条款17:考虑使用缓释评估

核心点:拖延战术

在你真正需要之前,不必着急为某物做一个副本

应用场景:

  • 引用计数
string s1 = "hello";
string s2 = s1;        //复制运算符如果直接做副本拷贝操作则称之为急式评估
                       //如果使用共享数据的方式则此操作仅仅是将引用计数加1/将指针指向同一个地方
  • 区分 operator[]的读和写动作:【暂记:跳转条款30】
  • 缓式取出

取出一个大型对象,对于有些程序,取出对象的所有数据消耗的成本非常大,因此可以特定的只取出个别的数据

class LargeObj
{
    public:
        const string& f1() const;
        int f2() const;
        ...
    private:
    mutable string f1;    //mutable:表示字段可以在任何成员函数中被修改,注意只能在成员函数中
    mutable int * f2;                
};

总结:缓式评估能避免那些非必要操作,但是它只是暂缓,如果计算是必要的它可能会起相反作用

条款18:分期摊还预期的计算成本-在被要求之前先把事情做下去

核心点:空间换时间

cacheing:   高速缓存-使用数据结构缓存应用过的数据

prefetching:预先取出-取出或配置更多未应用的数据/空间

条款19:了解临时对象的来源

所谓的临时对象是不可见的,即不会再源码中出现

{
    int i = 0;    //局部对象非临时对象
}


size_t countChar(const string& str,char c);

char buffer[10];
char c;

//buffer是数组,但是需要的参数为const string&,
//会做类型转换生成一个string(buffer),这个临时对象
//不可见,函数结束时将销毁
countChar(buffer,c); 

  • 函数调用时传值、传const 引用都会产生临时对象,非const引用则不会
  • 函数返回时也会产生临时对象

条款20:协助完成“返回值优化(RVO)”

核心点:尽量避免返回对象,如果非要返回,请使用return value optimization

几种不太正确的返回值样式:

const type* fun();    //不合理的返回,因为会导致资源泄漏
const type& fun();    //返回引用指向不存在的对象(非动态分配)

 最有效率的做法

class RVOTest
{
public:
	RVOTest(int i):data(i)
	{
		cout << "RVOTest(int " << i << ")"<<endl;
	}
	RVOTest(const RVOTest &other) : data(other.data)
	{
		cout << "copy constructor" << endl;
	}
	RVOTest& operator=(const RVOTest &rhs)
	{
		if (this != &rhs)
		{
			this->data = rhs.data;
		}
		cout << "operator = " << endl;
		return *this;
	}
	~RVOTest()
	{
		cout << "~RVOTest(int " << data << ")" << endl;
	}
	int data;
};

inline const RVOTest addRVO(const RVOTest& l1, const RVOTest& l2)
{
	//RVOTest r(l1.data + l2.data);			//会调用一次析构
	return RVOTest(l1.data + l2.data);
}
int main(int argc,char*argv[])
{
	RVOTest t1(2);
        RVOTest t2(3);
	auto t3 = addRVO(t1, t2);
	return 0;
}

RVO的原理是:将返回值当做该函数的参数处理

条款21:利用重载技术避免隐式类型转换

class UInt
{
public:
    UInt();
    UInt(int);
    const UInt operator+(const UInt& l,const UInt& r);
    const UInt operator+(int& l,const UInt& r);            //重载 成功
    const UInt operator+(const UInt& l,int& r);            //重载 成功
    const UInt operator+(int& l,int& r);                   //失败 
};




//代码段:
UInt t(10);
UInt t2;
UInt t3 = t + t2;         //成功
UInt t4 = 10 + t2;        //成功,隐式转换了

如上所示,可以通过重载实现对隐式转换的控制,上述失败标识是因为:

每个”重载操作符“,必须获得至少一个用户定制类型的自变量

条款22:考虑以操作符复合形式(op=)取代独身形式(op)

x = x + y   ->    x += y

要确保复合形式和独身形式之间的自然关系能够存在,比较好的方法是以前者为基础实现后者

Class Rational
{
    public:
       Rational& operator +=(const Rational& rhs); 
};

const Rational operator+ (const Rational& lhs,const Rational& rhs)
{
    return Rational(lhs) += rhs;        //调用复合式,同时使用了RVO优化
}

如果但从效率方面考虑,复合形式效率高,因为不会返回一个新的对象

忠告:对于程序库设计者,应该两者都提供,对于应用软件开发者,如果性能是最重要的因素应该使用复合式

条款23:考虑使用其它程序库

不同的程序库会将效率、扩充性、移植性、类型安全性等的不同设计具体化,可以适当根据自己的不同方面取舍应用不同的程序库

条款24:了解 虚函数、多继承、虚基类、执行期类型识别的成本

  • 虚函数表:

虚函数表是由函数指针架构而成的数组,某些编译器也会用链表取代数组,每一个class声明或者继承虚函数,都有一个vtl

如下示例:

class Base
{
    virtual ~Base();
    virtual void f1();
    virtual void f2();
};

class Realize:public Base
{
    virtual ~Realize();
    virtual void f1();
    virtual void f3(int);
};

两个类之间的虚函数表如下:

每个类只有一个vtbl,因此编译器只需要一份就好。

  • virtual table pointer(vptr)

有了虚函数表,实际上还并没有结束,只有通过某种方法可以指示出每一个对象对应于哪一个vtbl,它才真正有用,vptr就是用来完成这个任务的

假设C1和C2对象,其中C2继承自C1,两个对象和vptr、vtbls之间的关系如下图:

有如下程序段:

void makeCall(C1* pc)
{
    pc->f1();            //一个虚函数
}

编译器会完成以下动作:

  • 根据对象的vptr找到虚函数表位置
  • 找出被调函数(f1)在虚函数表对应的函数指针   【编译器为每个虚函数指定一个独一无二的表格索引】
  • 调用这个函数指针

注意事项:

虚函数不应该是inline,inline意味着编译期将调用端的调用动作被调用函数的函数本体取代(直接替换),virtual意味着运行期才知道哪个函数被调用

  • 多重继承如何找出对象内的vptrs

多重继承往往会有虚基类的需求,考虑以下的多重继承菱形图:

class A {};
class B:virtual public A{};
class C:virtual public A{};
class D:public B,public C{};

         A
      |     |
      B     C
         |
         D 

在A类有任意虚函数时,D对象的内存布局加上vptrs后会可能会变成如下图:

其中阴影部分是由编译器加入的,可以看到4个类却只有3个vptr,是因为B和D共享一个vptr,这样可以降低额外的开销

  • 运行时期类型辨识(RTTI)成本

RTTI在运行时期让我们获取到对象和类的相关信息,存放这些信息的类型为 type_info,可以使用typeid操作符获取某个class相应的type_info对象

type_info一般会结合虚函数表,其设计理念就是根据类的vtbl来实现,一般会将它放在vtbl数组中索引为0的条目里:

因此综合来说对于虚函数、多重继承、虚基类和RTTI,相比于C来说会有性能上的差异,导致对象大小增加等

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值