目录
条款19:了解临时对象的来源
条款20:协助完成“返回值优化”
条款21:利用重载技术避免隐式类型转换
条款22:考虑以操作符复合形式(op=)取代其独身形式(op)
条款23:考虑使用其他程序库
条款24:了解virtual functions、multiple inheritance、virtual base classes、runtime type identification的成本
条款25:将constructors和non-member functions虚化
条款26:限制某个class所能产生的对象数量
条款27:要求(禁止)对象产生于heap之中
条款28:智能指针(smart pointers)
条款19:了解临时对象的来源
产生临时对象通常发生于两种情况:
一是当隐式类型转换时,但请注意其中涉及到引用权限的问题,如果发生隐式类型转换,产生的临时变量具有常性,那么不能使用non-const类型进行接受,这属于权限放大;
二是当函数返回对象,需要进行返回值优化,
总结:临时对象很耗成本,因此你需要尽可能消除它们。
条款20:协助完成“返回值优化”
如果返回值在函数栈桢回收时销毁,那么你不能使用引用返回或者指针返回,此时取别名或是指针指向的是一个已经死亡的对象,没有任何意义,但如果出了函数作用域仍然没有销毁,那么可以考虑使用引用返回的方式。 如果函数一定要以by-value的方式返回对象,你无法 消除之,在不同的场景你需要选择正确且高效的返回值优化方式。
总结:在不同的场景你需要选择正确且高效的返回值优化方式。如果一定要传值返回,请不要刻意优化。
条款21:利用重载技术避免隐式类型转换
单参数的构造函数支持隐式类型转换, 那么这里建议你单独重载一个函数来进行特殊传 参,而不掉用隐式构造函数, 同时,请不要忘记增加太多的重载函数会影响程序效率。
条款22:考虑以操作符复合形式(op=)取代其独身形式(op)
第一, 一般而言,复合操作符比其对应的独身版本效率高, 因为独身版本通常必须返回一个新对象,而我们必须因此负担一个临时对象的构造和析构的成本,至于复合版本则是直接将结果写入其左端自变量,所以不需要产生一个临时对象来放置返回值。
第二, 独身版本较易撰写,调试,和维护,方便使用, 复合版本效率较高,你可以同时提供两个版本,并根据性能与便利性自由选择。
条款23:考虑使用其他程序库
不同的程序库即使提供相似的机能,也往往表现出不同的性能取舍策略,所以一旦你找出程序的瓶颈,你应该思考是否有可能因为改用另一个程序库而移除了那些瓶颈,可以找找看是否存在另一个功能相近的程序库而其在效率上有较高的设计权重。如果有,改用它,可以大幅 改善程序性能。
条款24:了解virtual functions、multiple inheritance、virtual base classes、runtime type identification的成本
当一个虚函数被调用时,编译器使用virtual tables和virtual table pointers(简写为vtbls和vptrs)。vtbl通常是一个函数指针数组,其中保存的是各个虚函数的函数指针, 使用虚函数的成本是一个vtbl数组空间以及一个额外指针vptr,以及类中的成员函数都有inline的属性,那么使用虚函数事实上等于放弃了inlining。 (如果虚函数通过对象被调用,倒是可以inlined,但大部分虚函数调用动作是通过对象的指针或者引用完成的,此类行为无法被inlined。)
上面是简单继承,再来看看多重继承,举例菱形继承,为了不引发二义性使用虚拟继承:

如图所示,虚基类可能导致对象内的隐藏指针增加,而如果基类A有任何虚函数,内存布局会
增加更多的指针(用红笔标出)。
条款25:将constructors和non-member functions虚化
这里说的当然不是将真正的构造函数虚化,如果你真的这么做了就会出大问题,由于虚函数 表是在初始化列表进行初始化的,那么如果你对函数进行虚化,那么需要先产生虚函数表,但虚 函数表在构造函数内部产生,这会导致程序混乱。好了, 这里是指将重复进行构造的动作单独设为一个函数,将此函数进行虚化,那么不同对象的构造函数来调用此函数就可以产生伪构造函数虚化的效果。
就像constructors无法真正被虚化一样。non-member functions也是,就像我们认为应该能够以某个函数构造出不同类型的新对象一样,我们也认为应该可以让non-member functions的行为视其参数的动态类型而不同。
条款26:限制某个class所能产生的对象数量
允许零个或一个对象:每当产生一个对象,就会调用这个class的构造函数,而阻止某个
class产出对象的最简单的方法就是将其constructors声明为private:
Class myclass
{
private:
myclass();
myclass(const myclass& mc);
}
但这样移除了每个人产出对象的权利,但我们也可以选择性地解除这项约束,单例模式可以分为: 饿汉模式以及懒汉模式,前者不管怎么样都会生成一份对象,且存在初始化顺序混乱的问
题,而后者可以在必要时进行初始化,节省空间, 大致方式是将类的对象和create函数设为
static,将构造函数隐藏,在create函数中调用构造函数来进行初始化…
//这里作者模拟实现了一下:仅供参考
namespace hungry
{class Singleton{public:static Singleton& Create(){return slt;}bool Add(const pair<string, string>& kv){hash.insert(kv);return true;}void print(){for (const auto& e : hash){cout << e.first << " " << e.second << endl;}}private://先将构造函数私有化,防止轻易构造新对象Singleton(){}//以hash表为例子unordered_map<string, string> hash;//如何确保只有一个对象?使用staticstatic Singleton slt;//!!!记住要防止拷贝Singleton& operator=(const Singleton& st) = delete;Singleton(const Singleton& st) = delete;};Singleton Singleton::slt;//这里有点小奇怪,就理解为用singleton实例化singletion的slt吧
}//懒汉模式:直接用指针代替,需要的时候再进行实例化
namespace lazy
{class Singleton{public:static Singleton& Create(){if (slt == nullptr){slt = new Singleton;return *slt;}else{return *slt;}}// 一般单例不用释放。// 特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化),比如写入文件static void Delete(){if (slt){delete slt;slt = nullptr;}else return;}bool Add(const pair<string, string>& kv){hash.insert(kv);return true;}void print(){for (const auto& e : hash){cout << e.first << " " << e.second << endl;}}class GC{public:~GC(){//垃圾回收站,这是为了方便多个单例对象需要持久化或者手动释放使用的Singleton::Delete();}};private://先将构造函数私有化,防止轻易构造新对象Singleton(){}~Singleton(){//在这里可以做一些持久化操作,将数据写入文件cout << "destructor" << endl;}unordered_map<string, string> hash;static Singleton* slt;static Singleton::GC gc;Singleton& operator=(const Singleton& st) = delete;Singleton(const Singleton& st) = delete;};Singleton* Singleton::slt=nullptr;Singleton::GC Singleton::gc;
}
不同的对象构造状态:你可以使用继承的方式实现不同对象的不同构造,但是要小心内存
泄露,可以使用智能指针来管理对象。
条款27:要求(禁止)对象产生于heap之中
允许对象产生于heap中:(禁止产生于栈中)
有两种方法:1.将析构函数私有,这样无法直接实例化出对象;
2.将构造函数私有,使用static Create函数来执行new operator操作返回指针;
//只能在堆上实例化
class Heaponly1
{
public:Heaponly1(){ }void Delete(){delete this;//巧妙}private:~Heaponly1(){//...}//要防止在栈上创建空间,还要将拷贝构造和复制构造deleteHeaponly1& operator=(const Heaponly1& hp) = delete;Heaponly1(const Heaponly1& hp) = delete;
};class Heaponly2
{
public:static Heaponly2* Create(){return new Heaponly2;}
private:Heaponly2(){//...}Heaponly2& operator=(const Heaponly2& hp) = delete;Heaponly2(const Heaponly2& hp) = delete;
};
禁止对象产生于heap中:(允许产生于栈中)
有两种方法:1.将operator new函数delete,这样new operator就无法调用operator new;
2.将构造函数私有,使用static Create函数来在栈上产生一个对象,并返回引用;
//只能在栈上创建对象的类
class Stackonly1
{
public:static Stackonly1 Create(){Stackonly1 st;return st;}
private:Stackonly1(){}
};class Stackonly2
{
public:Stackonly2(){ }
private:void* operator new(size_t size) = delete;
};
条款28:智能指针(smart pointers)
智能指针有auto_ptr、unique_ptr、shared_ptr、weak_ptr,其中auto_ptr存在指针悬空的 问题,unique_ptr只能管理一个对象,进行了反拷贝操作,shared_ptr使用引用计数,当计数减为0时释放内存,但是存在引用循环的问题,所以weak_ptr来专门解决引用循环的问题,不参与计数的改变,具体模拟实现可以参考作者往作。
C++ 智能指针底层逻辑揭秘:优化内存管理的核心技术解读-CSDN博客
https://blog.csdn.net/Dai_renwen/article/details/147230673?spm=1001.2014.3001.5501
