第二章 构造函数和析构函数
继承
当一个对象确实被需要的时候才创建它。
对象的创建(或销毁)触发对父对象和成员对象的递归创建(销毁)。要当心复杂层次中对象的复合使用。它们使得创建和销毁的开销更为高昂。
初始化成员变量使用显式构造。
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=运算符可以消除临时对象
单线程内存池
从分配大小角度分类
固定大小
分配固定大小内存的内存管理器
可变大小
分配任意大小内存块的内存管理器。所请求分配的大小事是未知的。
从并发角度考虑
单线程
内存管理器局限在一个单线程内。内存被一个线程使用,并且不越出该线程的界限。这种内存管理器不设涉及相互访问的多线程。
多线程
内存管理器被多个线程并发地使用。这种实现需要包含互斥执行的代码段。无论什么时候,只能有一个线程在执行一个代码段。