对象的创建和销毁往往会造成性能的损失。在继承层次中,对象的创建将引起其先辈的创建。对象的销毁也是如此。其次,对象相关的开销与对象本身的派生链的长度和复杂性相关。所创建的对象(以及其后销毁的对象)的数量与派生的复杂度成正比。

并不是说继承根本上就是代码性能的绊脚石。我们必须区分全部计算开销、必须开销和计算损失(computional penalty). 全部计算开销是一次计算中所执行的全部指令的总和。必须开销是全部指令的子集,它的结果是必要的。这部分计算是必需的,其余部分即为计算损失。计算损失是可以通过别的设计和实现来消除的那部分计算。

我们不能断言采用了复杂的继承的设计一定是坏的,也不能断定它们总是带来性能损失。我们只能说总的开销会随着派生树规模的增长而增加。如果所有的计算都是有价值的,那么它们都是必须的开销。实际上,继承层次不见得是完善的,在这种情况下,它们很可能会导致计算损失。

对象的复合与继承一样,都引入了与对象创建和销毁有关的类似性能问题。在对象被创建(或销毁)时,必须同时创建(或销毁)它所包含的成员对象。

创建和销毁被包含对象是另一个值得注意的问题:在创建(或销毁)被包含对象时无法阻止子对象的创建(或销毁),因为这是编译器自动强加的步骤。

性能优化经常需要牺牲一些其它软件目标,诸如灵活性、可维护性、成本和重用之类的重要目标经常必须为性能让步。

在C++中,不自觉地在程序开始处预先定义所有对象的做法是一种浪费。因为这样可能会创建一些直到最后都没有用到的对象。在C++中,把变量的创建延迟到第一次使用前。

构造函数和析构函数可以像手工编写的C代码一样有效。然而在实践中,它们经常包含冗余计算。

对象的创建(或销毁)触发对父对象和成员对象的递归创建(或销毁)。

要确保所编写的代码实际使用了所有创建的对象和这些对象所执行的计算。

对象的生命周期不是无偿的。至少对象的创建和销毁会消耗CPU周期。不要随意创建一个对象,除非你打算使用它。通常情况下,要等到需要使用对象的地方再创建它。

编译器必须初始化被包含的成员对象之后再执行构造函数体。你必须在初始化阶段完成成员对象的创建。这可以降低随后在构造函数部分调用赋值操作符的开销。在某些情况下,这样也可以避免临时对象的产生。

以下是测试代码(constructors_and_destructors.cpp):

#include "constructors_and_destructors.hpp"
#include <iostream>
#include <string>
#include <mutex>
#include <chrono>namespace constructors_destructors_ {// reference: 《提高C++性能的编程技术》:第二章:构造函数和析构函数class SimpleMutex { // 单独的锁类
public:SimpleMutex(std::mutex& mtx) : mymtx(mtx) { acquire(); }~SimpleMutex() { release(); }private:void acquire() { mymtx.lock(); }void release() { mymtx.unlock(); }std::mutex& mymtx;
};class BaseMutex { // 基类
public:BaseMutex(std::mutex& mtx) {}virtual ~BaseMutex() {}
};class DerivedMutex : public BaseMutex {
public:DerivedMutex(std::mutex& mtx) : BaseMutex(mtx), mymtx(mtx) { acquire(); }~DerivedMutex() { release(); }private:void acquire() { mymtx.lock(); }void release() { mymtx.unlock(); }std::mutex& mymtx;};class Person1 {
public:Person1(const char* s) { name = s; } // 隐式初始化和显示赋值private:std::string name;
};class Person2 {
public:Person2(const char* s) : name(s) {} // 显示初始化private:std::string name;
};int test_constructors_destructors_1()
{// 测试三种互斥锁的实现// Note:与书中实验结果有差异,在这里继承对象并不会占用较多的执行时间,在这里这三种所占用时间基本差不多using namespace std::chrono;high_resolution_clock::time_point time_start, time_end;const int cycle_number {100000000};int  shared_counter {0};{ // 1.直接调用mutexstd::mutex mtx;shared_counter = 0;time_start = high_resolution_clock::now();for (int i = 0; i < cycle_number; ++i) {mtx.lock();++shared_counter;mtx.unlock();}time_end = high_resolution_clock::now();std::cout<< "time spen1: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<< " seconds\n";
}{ // 2.不从基类继承的独立互斥对象std::mutex mtx;shared_counter = 0;time_start = high_resolution_clock::now();for (int i = 0; i < cycle_number; ++i) {SimpleMutex m(mtx);++shared_counter;}time_end = high_resolution_clock::now();std::cout<< "time spen2: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";
}{ // 3.从基类派生的互斥对象std::mutex mtx;shared_counter = 0;time_start = high_resolution_clock::now();for (int i = 0; i < cycle_number; ++i) {DerivedMutex m(mtx);++shared_counter;}time_end = high_resolution_clock::now();std::cout<< "time spen3: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";}// 隐式初始化和显示赋值与显示初始化性能对比:使用显示初始化操作要优于使用隐式初始化和显示赋值操作
{ // 1.隐式初始化和显示赋值操作time_start = high_resolution_clock::now();for (int i = 0; i < cycle_number; ++i) {Person1 p("Pele");    }time_end = high_resolution_clock::now();std::cout<< "隐式初始化, time spen: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";
}{ // 2.显示初始化操作time_start = high_resolution_clock::now();for (int i = 0; i < cycle_number; ++i) {Person2 p("Pele");}time_end = high_resolution_clock::now();std::cout<<"显示初始化, time spen: "<<(duration_cast<duration<double>>(time_end - time_start)).count()<<" seconds\n";
}return 0;
}} // namespace constructors_destructors_

运行结果如下:

GitHub: https://github.com/fengbingchun/Messy_Test

提高C++性能的编程技术笔记:构造函数和析构函数+测试代码相关推荐

  1. 提高C++性能的编程技术笔记:编码优化+测试代码

    缓存:在现代处理器中,缓存经常与处理器中的数据缓存和指令缓存联系在一起.缓存主要用来存储使用频繁而且代价高昂的计算结果,这样就可以避免对这些结果的重复计算.如,循环内对常量表达式求值是一种常见的低性能 ...

  2. 提高C++性能的编程技术笔记:引用计数+测试代码

    引用计数(reference counting):基本思想是将销毁对象的职责从客户端代码转移到对象本身.对象跟踪记录自身当前被引用的数目,在引用计数达到零时自行销毁.换句话说,对象不再被使用时自行销毁 ...

  3. 提高C++性能的编程技术笔记:内联+测试代码

    内联类似于宏,在调用方法内部展开被调用方法,以此来代替方法的调用.一般来说表达内联意图的方式有两种:一种是在定义方法时添加内联保留字的前缀:另一种是在类的头部声明中定义方法. 虽然内联方法的调用方式和 ...

  4. 提高C++性能的编程技术笔记:临时对象+测试代码

    类型不匹配:一般情况是指当需要X类型的对象时提供的却是其它类型的对象.编译器需要以某种方式将提供的类型转换成要求的X类型.这一过程可能会产生临时对象. 按值传递:创建和销毁临时对象的代价是比较高的.倘 ...

  5. 提高C++性能的编程技术笔记:跟踪实例+测试代码

    当提高性能时,我们必须记住以下几点: (1). 内存不是无限大的.虚拟内存系统使得内存看起来是无限的,而事实上并非如此. (2). 内存访问开销不是均衡的.对缓存.主内存和磁盘的访问开销不在同一个数量 ...

  6. 提高C++性能的编程技术笔记:总结

    <提高C++性能的编程技术>这本书是2011年出版的,书中有些内容的介绍可能已经过时,已不再适用于现在的C++编程中,但大部分内容还是很有参考意义的. 这里是基于之前所有笔记的简单总结,笔 ...

  7. 提高C++性能的编程技术笔记:标准模板库+测试代码

    标准模板库(Standard Template Library, STL)是容器和通用算法的强效组合. 渐近复杂度:算法的渐近复杂度是对算法性能的近似估计.它是算法集到特定性能标准集的映射.如果需要对 ...

  8. 提高C++性能的编程技术笔记:多线程内存池+测试代码

    为了使多个线程并发地分配和释放内存,必须在分配器方法中添加互斥锁. 全局内存管理器(通过new()和delete()实现)是通用的,因此它的开销也非常大. 因为单线程内存管理器要比多线程内存管理器快的 ...

  9. 提高C++性能的编程技术笔记:单线程内存池+测试代码

    频繁地分配和回收内存会严重地降低程序的性能.性能降低的原因在于默认的内存管理是通用的.应用程序可能会以某种特定的方式使用内存,并且为不需要的功能付出性能上的代价.通过开发专用的内存管理器可以解决这个问 ...

最新文章

  1. 所引用的程序集没有强命名解决方法
  2. 把女友升级为老婆的时候发生的BUG(转载)
  3. 浅析C#发送短信的原理
  4. 创建WebSocket服务器
  5. mysql执行shell命令_关键Docker命令:使用Docker必须掌握的公认宝典
  6. vue怎么把api 挂载到全局_nvue实现全局挂载
  7. 2016年10个重要的可视化发展
  8. 安装插件设置Intellij IDEA背景图片
  9. Material Design设计技巧
  10. 基于rhel7.2的mysql5.7.13安装与配置
  11. oracle和plsqldev的安装,win7x64下成功安装ORACLE客户端和PLSQLDEV!
  12. 帆软报表填报成功后实时刷新当前页面
  13. js实现html转图片保存
  14. 去世父亲在儿子手机中复活,这可能是最温暖的一个AI
  15. 蓝桥杯 灭鼠先锋 博弈
  16. python plot坐标轴显示比例一致_绘图,x轴和y轴的比例相同
  17. Android手机主流屏幕分辨率有哪些?
  18. BBS 与 BLog(博客)的区别到底是什么?[转载]
  19. 九大PHP开源Wiki(维基百科)程序评测
  20. 第四天--外边距塌陷

热门文章

  1. 记录Mask RCNN调整预测网格 font大小
  2. 通信系统未编码、卷积码与格雷码的仿真性能比较
  3. Matlab中的lsqcurvefit函数的使用
  4. php ob_flush无效,php ob_flush,flush在ie中缓冲无效的解决方法
  5. 【从零开始的ROS四轴机械臂控制】(二) - ROS与Gazebo连接,Gazebo仿真及urdf文件修改
  6. 设置VSCode终端命令行清除快捷键Ctrl+K或Ctrl+L
  7. 《中国式方案秘籍(下部)》
  8. 使用最小堆使用优先级队列(c语言版本)
  9. C++中的string::compare的使用
  10. 如何让插件代码同时兼容ats 5.x和ats 6.x的方法