提高C++性能的编程技术

第二章 构造函数和析构函数

继承

当一个对象确实被需要的时候才创建它。
对象的创建(或销毁)触发对父对象和成员对象的递归创建(销毁)。要当心复杂层次中对象的复合使用。它们使得创建和销毁的开销更为高昂。
初始化成员变量使用显式构造。

class FTest
{
public:
   FTest(const std::wstring &str)
    :_str(str)//建议
   {
	//   _str = str;//不建议
   }
 private:
    std::wstring _str;
};

第三章 虚函数

虚函数的构造

  • 构造函数必须初始化vptr(虚函数表指针)
  • 虚函数是通过指针间接调用的,所以必须先得到指向虚函数表的指针,然后再获得正确的函数偏移量
  • 内联是在编译时决定的,编译器不可能把运行时才解析的虚函数设置为内联。

模板和继承

只能在运行期间解析的虚函数是不允许使用内联的。因为函数调用的动态绑定是继承的结果。所以消除动态绑定的一种方法是使用基于模板的设计来替代继承。模板把解析的步骤从运行期间提前到了编译期间

线程安全string类案例

预备类

class Locker
{
public:
	Locker(){}
	virtual ~Locker(){}
	virtual void lock() = 0;
	virtual void unlock() = 0;
};
class CirticalSectionLocker : public Locker
{
public:
	CirticalSectionLocker(){}
	~CirticalSectionLocker(){}
	virtual void lock()override
	{
		//临界区方式加锁
	}
	virtual void unlock()override
	{
		//临界区方式解锁
	}
};
class MutexLocker : public Locker
{
public:
	MutexLocker(){}
	~MutexLocker(){}
	virtual void lock()override
	{
		//互斥锁方式加锁
	}
	virtual void unlock()override
	{
		//互斥锁方式解锁
	}
};
class SemaphoreLocker : public Locker
{
public:
	SemaphoreLocker(){}
	~SemaphoreLocker(){}
	virtual void lock()override
	{
		//信号量方式加锁
	}
	virtual void unlock()override
	{
		//信号量方式解锁
	}
};

在派生自string类的基础上,以如下三个角度来设计
示例代码,只是辅助理解,不一定能够编译通过
硬编码
从string类中派生出 CriticalSectionString,MutexString和SemahoreStrintg。每个类实现各自的同步机制。

class MutexString : public std::string
{
public:
    int getLength()
	{
		int length = 0;
		_mutexLocker.lock();
		length = std::string::length();
		_mutexLocker.unlock();
		return length;
	}
private:
MutexLocker  _mutexLocker;
}

此种设计在性能上具有优势。通过虚函数来调用正确的lock以及unlock方法。但是此设计的不足之处在于需要为每种同步机制编写各自的string类。导致代码重用性较低
继承
派生出一个单独的ThreadSafeString类。它包含指向LocKer对象的指针,在运行期间通过多态机制选择特定的同步机制。

class ThreadSafeString : public string
{
public:
     ThreadSafeString(const char *str,Locker *locker)
	   :std::string(str),_locker(locker)
	 {
		 
	 }
	 int getLength()
	 {
		 _locker->lock();//未进行判空
		 int length = std::string::length();
		 _locker->unlock();
		 return length;
	 }
private:
  Locker* _locker;
}
{
   MutexLocker  locker = new MutexLocker ;
   ThreadSafeString  safeStr("ABC",locker);
}

虚函数调用lock以及unlock仅在执行期间解析,因此不能对它们内联。从而带来了性能的损失。
模板
基于模板的string类,该类由Locker类型参数化后得到

template<class LOCKER>
class ThreadSafeString : public string
{
public:
	ThreadSafeString(const char *str)
		:std::string(str)
	{

	}
	int getLength();
private:
	LOCKER  _locker;
};
template<class LOCKER>
inline int ThreadSafeString<LOCKER>::getLength()
{
	_locker.lock() :
		int length = std::string::length();
	_locker.unlock();
	return length;
}
{
 ThreadSafeString<MutexLocker> safeString = "Hello":
 int len = safeString.getLength();
}

这种设计也避免了对lock以及unlock的虚函数调用。ThreadSafeString声明在实例化模板时选择特定的同步类型。如同硬编码一样,它使编译器可以解析这两个虚函数调用并且内联。
模板计算从执行期间提前到编译期间来做,并且在编译时使用内联,因此提高了性能。

第四章 返回值优化

如果必须按照值返回对象,通过RVO可以省略创建和销毁局部对象的步骤,从而改善性能

第五章 临时对象

按值传递

class Test 
{
public:
  Test(){}
  ~Test(){}
};
void functionPassValue(Test test)
{
	//....
}//此种方式 编译器将创建一个Test类型的临时对象。并且使用test作为输入参数来复制构造它(临时对象)。然后临时对象作为实参传递给functionPassValue 该新创建的临时对象将按引用方式传递给functionPassValue
void functionPassReferences(Test &test)
{
	//...
}
void functionPassPointer(Test *test)
{
}//按照指针以及引用方式不会产生临时对象。

创建和销毁临时对象的代价是比较高的。倘若可以,应该按指针或者引用来传递对象以避免生成临时对象

按值返回

如果编写的函数是按值返回对象(与引用或者指针相对),就很可能产生临时对象。
std::wstring getWStringByReturnValue()
{
std::wstring str;
//…
return str;
}//编译器生成一个临时对象来存储返回值
关于 std::string的 + 运算符

std::string s1 = "Hello";
std::string s2 = "World";
std::string s3;
s3 = s1 + s2;//产生一个临时对象
std::string s3 = s1+s2;//不会产生临时对象

为什么产生临时对象
因为我们没有权利修改s3的旧内容并使用 s1+s2的新内容来覆盖它。赋值运算
符(=)负责把 s3 由旧内容变为新内容。然而编译器不允许跳过std::string::operator=(),因此必须生成临时对象。但如果s3是没有旧内容的新对象呢?在这种情况下,就无须担心旧内容。此时编译器可以使用s3而不是临时对象来存储。s1+s2的结果直接复制构造至s3对象中。s3取代了不再必须的临时对象

要点总结

临时对象会以构造函数和析构函数的形式降低一半的性能。
将构造函数声明为 explicit ,可以组织编译器在幕后使用类型转换
编译器常常创建临时对象来解决类型不匹配问题。通过函数重载可以避免这种情况
如果可能, 应该尽量避免使用对象拷贝(函数返回按值返回,函数实参为值传递)。按引用传递和返回对象
在 operator 可能是 "+、-、*“或者”/"的地方。使用 operator=运算符可以消除临时对象

单线程内存池

从分配大小角度分类

固定大小
分配固定大小内存的内存管理器
可变大小
分配任意大小内存块的内存管理器。所请求分配的大小事是未知的。

从并发角度考虑

单线程
内存管理器局限在一个单线程内。内存被一个线程使用,并且不越出该线程的界限。这种内存管理器不设涉及相互访问的多线程。
多线程
内存管理器被多个线程并发地使用。这种实现需要包含互斥执行的代码段。无论什么时候,只能有一个线程在执行一个代码段。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一路初心向前

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值