自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(51)
  • 收藏
  • 关注

原创 智能指针之设计模式6

智能指针所用的设计模式思想1、工厂方法模式创建sp除了常规的使用构造函数之外,还提供了两个工厂方法。sp的make_shared() 来生成一个shared_ptr对象,是简单工厂模式。把使用视频和创建sp的职责分离了,工厂方法能够控制对象的生成过程,在这里,make把资源对象和控制对象在一个内存块中了,这样可以更好的提高cache line的预取了。sp把托管资源对象和控制块(头部信息)分配在一起了,故不能在mak_shared中指定deleter对象。enable_shared_from_th

2025-04-28 19:34:11 685 1

原创 智能指针之设计模式5

这里的问题是内存资源是使用二维指针来初始化和分配的,但是智能指针只能管理一维指针,getline()的参数需要二维指针,而unique_ptr::get()返回的是一维指针,无法对它进行取地址操作&。从这些例子中,我们可以发现,智能指针管理的裸指针都是一维指针类型,但是有一些场合需要使用二维指针,比如在C语言中,因为没有引用类型,一个函数在对指针做出修改时,往往需要使用二维指针作为它的参数类型,在函数内部来初始化、修改或者销毁指针参数指向的内存资源,此时,就无法直接使用智能指针进行管理了。

2025-04-27 09:37:57 692

原创 智能指针之设计模式4

前面的介绍了使用工厂模式来封装智能指针对象的创建过程,下面介绍一下工厂类enable_shared_from_this的实现方案。

2025-04-22 20:51:53 894

原创 智能指针之设计模式3

这次我们看一下智能指针是如何使用策略模式来释放资源的,同时又是如何扩展功能,管理更多的资源对象类型的。

2025-04-22 20:48:47 773

原创 汇编语言中的数据

虽然在前面我把数据分成了三部分进行说明,实际上它们三者是同时密切联系在一起的。1、源数据位于内存中,是一个“基址+变址*因子+偏移量”的寻址方式,位置是经过基址寄存器rdi和变址寄存器rsi运算后的内存地址,数据长度是4字节(dword ptr指示),而地址数据存放在寄存器rdi中,它的长度是8字节,相当于rdi对应C/C++中的指针类型;2、目的数据位于寄存器edx中,它的长度是4字节3、指令操作符是add,是算数加法操作,因此源数据和目的数据都是整型数,但是有符号还是无符号数仍不知道。int x。

2025-04-18 21:55:14 1020

原创 优化自旋锁的实现

1、TAS算法实现自旋锁,会导致内存总线流量风暴,全局系统影响大。2、TTAS虽然抑制了流量风暴的产生,减轻了全局内存总线的竞争程度,但是又导致CPU耗电量大、发热等情况。3、使用pause指令缓解了超线程核心的系统资源竞争和降低了耗电量,但是又增长了获取锁时的延迟。4、TTAS算法和pause指令是在程序和CPU上面对自旋锁的优化,如果获取不到锁时,线程仍然处于自旋中,不会发生调度和阻塞现象,没有改变自旋锁的本质特征。

2025-04-18 17:34:14 752

原创 智能指针之设计模式2

前面介绍了控制了智能指针和资源对象的创建过程,现在介绍一下智能指针是如何利用代理模式来实现“类指针(like-pointer)”的功能,并控制资源对象的销毁过程的。

2025-04-13 22:32:45 1087

原创 智能指针之设计模式1

本文探讨一下智能指针和GOF设计模式的关系,如果按照设计模式的背后思想来分析,可以发现围绕智能指针的设计和实现有设计模式的一些思想体现。当然,它们也不是严格意义上面向对象的设计模式,毕竟它们没有那么分明的类层次体系,和GOF经典设计模式在外在形式上有所差别,重点是理解设计模式的思想在它们身上的体现,以及怎样帮助它们实现意图的。限于篇幅,分成了几篇文章来介绍,先从对象的创建开始。

2025-04-13 18:57:14 809

原创 栈和局部变量-拾遗

最近翻到了一些是与函数栈相关的笔记,补充一下栈安全/预留空间的知识,算是文章的拾遗吧。

2025-04-10 16:45:38 994

原创 C++11实现一个自旋锁

自旋锁也是一种互斥锁,和mutex锁相比,它的特点是不会阻塞:如果申请不到锁,就会不断地循环检测锁变量的状态,直到申请到锁。它的核心算法是一个循环检查锁变量的操作,是CPU自我循环的过程,因此称为自旋锁。底层实现有两种算法,一种是把目标值和锁状态变量的原值进行交换,这个过程要求是一个原子操作,然后检查交换出来的值是否是期望的值,称为TAS算法;另一种是先检查锁状态变量是否是期望值,如果是就设置为目标值,并返回成功,这个过程也要求是一个原子操作,称为CAS算法。

2025-04-07 20:55:26 851

原创 为什么函数对象作为函数参数时,一般使用值类型形式?-番外篇

而nmax的第1个参数是回调函数comp的函数指针,第2个参数a是comp的第1个参数,第3个参数b是comp的第2个参数,因此,在nmax中调用comp时,需要把nmax的第2个参数rsi和第3个参数rdx分别赋值到rdi和rsi寄存器中,以符合调用约定,同时第1个参数rdi存放的是comp的函数指针,还得要把这个rdi寄存器腾让出来,以存放comp的第1个参数,因此会有更多的指令来调整这些参数的位置存放。因为要使用rdi寄存器来传递this指针,需要对rdi的原值进行缓存,不得不花费额外的指令。

2025-04-03 16:04:22 865

原创 为什么函数对象作为函数参数时,一般使用值类型形式?

总之,空函数对象作为参数时,在按值和按引用传参时的开销几乎是一样的,无非是按值传参时先不传递函数对象,延迟到在算法函数调用回调函数对象时再临时创建和传递函数对象,而按引用传参时在调用算法函数时就创建了函数对象,无非就是先做还是后做的区别,二者性能几乎完全一样。算法是函数模板,编译器在编译过程中可以看到算法实现的源码,如果在调用时对它进行了内联,同时编译器也知道所设置的回调函数的源码,那么就意味着整个调用链路编译器都是可见的,也就说无论是传递值还是引用,在内联优化展开代码时,没有区别,因此生成了一样的代码。

2025-04-03 13:09:37 687

原创 巧用临时对象之五

这样,如果后续再有读者进行读操作时,通过这个已经更新的全局指针来访问vector就可以了,同样,如果后面再有新的写操作,继续创建一个新的vector对象并进行更新,然后让全局指针指向它。不过,在实践中最好不要使用auto定义变量来接收那个代理对象,如果类型转换时,直接让一个具体类型的变量来接收临时代理对象,如果是调用其它操作符时如“->”,则直接调用,这样代理对象就会自动进行类型转换或者调用其它操作符,因为代理对象是匿名的临时对象,用户也就意识不到是在和一个代理对象打交道,实现的代码看起来也非常干净利索。

2025-04-02 17:13:54 708

原创 编译器眼中的-标量对象和空对象的传参及优化

C++编译器在编译程序时,只要不违背C++语义,可以对程序进行一些变换或者优化。如果C++对象是一些特殊类型的对象,当它们是一个函数的参数(当然也包括成员函数,因为成员函数隐藏着一个this指针,在调用时把对象自己作为参数传递)时,编译器在传递参数时可以进行一些特殊的优化,以提高传递函数参数和访问数据成员的性能。

2025-03-27 18:59:14 1023

原创 编译器眼中的虚函数动态绑定

根据前面的图示,vptr存放在一个对象this指针指向的对象实例中,而对象的this指针的值,只能在运行时才能知道,因此要得到虚函数,那也就得在程序运行的时候了,这个阶段也只能在运行时,无法在编译时静态绑定,所以也就是所谓的动态绑定。综上,动态绑定并不神秘,形象一点说,就是编译时构造了一个绑定虚函数的“公式”,该“公式”在编译的时候确定它的常量系数,在运行的时候得到它的和实际类型相关的动态参数,进行计算后,就能得到实际类型的虚函数指针,是编译器编译时的静态分析和程序运行时的动态查找,它们共同合作的结果。

2025-03-27 16:21:48 536

原创 cmpxchg16b指令的实现分析

我们知道C++11以后提供了原子操作类型:atomic<T>,该原子模板类可以实现原子操作,比如exchange、compare_exchange_weak、fecth_add等,T类型一般都是非常简单的类型,比如整型、指针等类型,这些类型都可以实现指令级的原子操作,所操作的数据类型的长度一般也不能大于内存数据总线的宽度,比如在32/64bit的处理器中,T类型的数据长度不能超过32/64bit。它会比较指针ptr指向的值与给定的旧值oldval,如果相同,则将新值newval写入,并返回真(true);

2025-01-24 19:00:02 980

原创 巧用临时对象(四)

因为它的数据成员都是引用类型,销毁对象时也无需管理资源,过程非常简单,实际上是对临时对象进行了“去皮留瓤”的操作,即在外在形式上,虽然临时对象(皮)销毁后,但在变量x、y、z中却留下了临时对象的各个数据成员的值(瓤),也就是实现了对my_data对象的结构化绑定。那么,我们转换一下思路,把需要绑定的各个独立变量,作为某个类的数据成员,然后重载这个类的赋值操作符operator=,参数是my_data类型,这样,在赋值操作符函数中就可以把my_data对象各个数据成员赋值给每个独立变量了。

2024-12-19 10:55:31 908

原创 巧用临时对象(三)

每当调用sync_shared_ptr的operator->()操作符时,都会创建并返回一个内部类raii的临时对象,因为这个临时对象也重载了操作符->,所以继续调用它的operator->()操作符,最后返回了sync_shared_ptr所保存的裸指针,该指针指向一个vector对象,使用->操作符可以访问它所指向的vector对象的成员函数。对象obj调用locked()函数返回的值被赋值给locked_ptr对象,此时就不再是临时对象了,而是一个具名对象,它的生存期是由作用域决定的。

2024-12-16 16:48:19 602

原创 巧用临时对象之二

对于id来说,因为是基本类型的数据,这点开销并不大,尚可接受,而对于name成员,它是string类型,因为不同库的实现不同,在构造时有的库实现可能需要更多的耗时操作。按照临时对象的生存期定义,它的生存期仅存在于lock_guard<mutex>(source.mtx)表达式所在的这一行,即在调用委托构造函数传递实参时,创建lock_guard的临时对象,并同时进行加锁,当调用返回后表达式生存期结束,这时销毁lock_guard临时对象,并同时进行解锁。

2024-12-14 06:00:21 827

原创 巧用临时对象之一

就以类universal_ptr来说,它只有一个数据成员,而且还是指针类型,指针占用内存非常小,在64位环境下仅占用8字节,而是这个类的析构、拷贝构造和移动构造函数都是缺省的,只是简单的指针赋值操作而已,因此,尽管在调用的中间阶段产生了临时对象,但开销非常小。此外,临时对象使用指针形式的数据成员,也无需担心悬挂指针的问题,因为所创建的临时对象,它即时传参即时创建即时销毁,都是发生在同一个表达式中,它的生存期不会晚于创建时所传递的参数的生存期。时,返回值类型被推导为unique_ptr<string>类型?

2024-12-13 20:20:43 759

原创 探索连续调用多个虚函数的优化

在函数B::foo()中,使用placement new操作符在B类型的对象所占用的内存空间中,创建了它的一个派生类D的对象(二者的sizeof相等,完全可以这样做),因为在调用D的构造函数时,要初始化vptr指针,此时vptr被修改为指向D类的虚函数表,尽管此时B所创建的对象的this指针没有变化,但是因为B和D的对象布局完全一样,this所包含的vptr指向的虚函数表已经被修改成了D的虚函数表。原因是为了安全,尽管虚函数表指针是不可见的,但在程序中仍然可以通过非常规的手段对它进行修改。

2024-11-20 21:49:22 542

原创 栈和局部变量

我们知道程序在运行时,它的内存可以分为数据区、代码区、堆区、栈区等,而栈区是一个线程独占内存区。一个函数分配的局部变量就是在所在线程的栈区分配的,当调用一个函数时,会为它建立一个栈桢,所有函数用到的局部变量都是在这个栈帧中分配的,局部变量所占用的内存空间分配和释放都是自动的,调用函数时随着栈桢的创建,局部变量的空间也就分配好了,当函数退出时释放栈桢,局部变得空间也就释放了,所有的这一切都是自动进行的,无需程序员参与。先看一下栈的运行机制,以x86 32 位 CPU 为例。在刚开始工作在实模式时,CPU中

2024-11-01 10:59:37 592

原创 派生类重载的delete操作符调用时可以动态绑定吗

该函数先调用了dog类的析构函数,然后再调用dog类的重载的operator delete(),正好符合delete操作符的语义,也就是说在这里,编译器使用了一个独立的函数来封装了这个delete操作符的功能。因此,派生类中重载的delete操作符在使用基类指针析构堆上对象时,也是动态绑定来调用的,只不过它并不是使用传统的方式,定义成虚函数来动态绑定的,而是被封装在一个编译器自动生成的虚析构函数中,通过动态绑定虚析构函数来间接的动态绑定。

2024-10-31 15:35:49 885

原创 C++中的CRTP

我们知道,要定义派生类肯定需要知道基类的类型,而在使用类模板来实例化一个模板类时又得需要知道模板的实参类型,在这里实例化基类时的实参类型又是派生类的类型。CRTP基类是类模板,基类实例化之后,和派生类是一一对应的,即一个基类只能派生一个子类,而模板参数化this指针的实现方式是基类只有一个,而且是非模板类,它的派生类可以有多个,但是每定义一个派生类,在该基类中都会使用派生类类型实例化出一个对应的interface()函数,也就是在基类中会有多个interface()成员函数和它的派生类一一对应。

2024-10-18 22:48:33 1594

原创 一种条件语句的编译优化方式

我们知道,指令是以指令流的形式在CPU流水线中解码并执行的,如果指令流的流程没有发生跳转的话,它就会按照指令流中的指令顺序依次执行,并且在前面指令的执行过程中,CPU的解码单元还会预取后面的指令并预先进行译码操作,前面的指令执行完毕紧接着执行已经译码完成的后面的指令,一环扣一环的向前推进。=的比较时,编译器会认为不相等是一个大概率的事件,显然int型的取值范围是0-4G,因此如果a和b的值是均匀分布的话,它们相等的可能性是1/4G,在编译器看来是极低的概率。=b),编译器都生成更有利于a!

2024-10-05 20:38:41 628 1

原创 C++ string类能否被继承?

对于像string这样没有虚函数的类,如果要复用它的功能,首先应该考虑使用对象组合的机制,让string对象是派生类的一个成员,在派生类中通过转发的方式使用string的成员函数。原因是在堆上创建的MyString对象是通过它的基类string类型来delete的,然而string的析构函数并不是virtual函数,这样在delete pStr时,调用的是string的析构函数,并没有调用派生类MyString 的析构函数,从而导致了MyString的数据成员prefix所分配的内存没有被释放。

2024-10-05 16:52:07 1183

原创 当C++遇到空指针异常......

在Java语言中,有空指针异常,在编程时为了代码安全,在遇到空指针时,防止程序崩溃,会捕捉空指针异常,即NullPointerException异常类。比如下面就是一段捕获NullPointerException异常的Java代码片段:try { 。。。。} catch (NullPointerException e) { e.printStackTrace();}当try语句块中的代码访问到空指针后,会抛出NullPointerException,随后在catch语句捕获这个异常,并

2022-04-28 18:24:49 10290 1

原创 C++11实现一个读写自旋锁-3(顺序锁 )

是一种特殊的读写锁,它也是一种乐观锁,我们知道,在读写锁中读写操作之间是互斥的,然而在顺序锁中,读写之间没有锁,写操作的时候无视读者的存在,但是读者在读数据时,要进行校验,验证在读数据期间数据没有被修改过,如果修改了,就放弃已经获取的数据,重新获取数据,也就是说读者总是假定它所读取的数据是正确的,是一种读乐观锁。同lock-free相比,它的关注点是读,只要在读的那一时刻,没有写操作,就认为能够都成功,读完之后再判断在读的过程中是否有写操作发生,如果有,则回滚,重新读。而lock-free更常见的是写操作,

2022-04-28 09:10:11 1328

原创 C++中调用虚函数都是动态绑定吗

多态在面向对象编程中是一个重要的概念,一般是使用虚函数来实现的,原理就是通过虚函数表保存了一个类的虚成员函数的指针,在调用虚函数时,可以通过对象的虚函数表指针来从虚函数表中得到函数指针,因为函数地址是通过函数指针来访问的,所以在编译时是不知道函数入口地址的,只能在运行时通过访问虚函数表来定位。为了方便分析说明,先定义几个有继承关系的类:// 基类class base {public: base() {} base(int) { foo(); } virtual void f

2022-03-17 17:36:19 1354

原创 C++中值语义的函数参数和返回值的背后

我们知道,在C++中对象作为数据传递时,如函数的参数传递和返回值,有值语义和引用语义的形式之说。当使用基本数据类型(如int、short、bool、指针、引用)进行传递时,实现非常简单,因为它们的值可以直接放入寄存器中,把寄存器作为参数传递和返回值的载体,就可以了,从一些函数调用约定就知道这些情况。可是,如果要传递的数据不是基本类型,而是用户定义的类型,比如class、struct等复合数据类型,这些类型的数据是无法放入一个寄存器的,那么在C++中是如果实现的呢。比如,我们有一个类,如下:class.

2022-03-17 11:26:32 1103

原创 C++11实现一个读写自旋锁-2

方案2class rw_spin_lock {public: rw_spin_lock() = default; ~rw_spin_lock() = default; rw_spin_lock(const rw_spin_lock&) = delete; rw_spin_lock &operator=(const rw_spin_lock&) = delete; void lock_reader() noexcept; void unlock_reader()

2021-12-21 10:21:33 335

原创 C++11实现一个读写自旋锁-1

本文介绍两种使用自旋锁方式实现读写锁的方案。方案一基本原理是使用一个原子变量作为计数器,如果该计数器的值大于0,说明有读者在持有读锁,它的值就是读者的数量,当计数器的值为-1时,说明有写者在持有写锁,如果计数器的值为0,则说明既没有读者持有读锁,也没有写锁持有写锁。所提供的接口有申请读锁,尝试申请读锁,释放读锁,申请写锁,尝试申请写锁,释放写锁等。定义读写锁rw_spin_lock类如下,它包含一个atomic_int类型的数据成员和六个申请锁、释放锁的成员函数,没有拷贝和移动语义。在实现时,要注意申

2021-12-16 21:34:08 1866

原创 C++11实现一个cyclic barrier

举个生活中的例子,假如有5个好基友,商量好周末一块去爬山,他们约定周日早上8点在山脚下集合,不见不散。周日那天,如果一个先到了,发现没有其他人到达,就只好等着,第二个人到了之后,发现还没有到期,也只能等待,直到第5个人到达后,所有5个人全部到齐了,就一起出发开始爬山。如果我们在脑海中想象有一个栅栏(barrier)立在山门口,如果它不拿走,人们是无法越过去的,它被拿走的条件是,5个基友全部到达。如果这5个基友哪怕仅有一个还没有到达,它也不会被放倒,基友就被拦在外面,只有当5个基友全部到达之后,这个栅栏才会陡

2021-12-15 21:26:26 1481

原创 C++11实现一个countdown latch

在日常生活中,我们经常遇到这样场景,当要做一件事情时,要先等待几个固定数目的其它事情做完了,才能进行,如果别的事情没有就绪,只能等待。比如,一个工厂有存放原材料的仓库,仓库的大门共有三把锁,分别由仓管员、主管部门经理和值班经理保管,当一个(或几个)领料员上班后去仓库领材料,发现仓库大门锁着,那么他就只能等着这三个掌管钥匙的人员来开门:如果主管部门经理来上班了,就把他负责的那把锁打开,然后去工作了,此时领料员只能继续等待,当值班经理来上班之后,把他负责的那把锁打开,然后也去工作了,只有最后仓管员上班之后打开最

2021-12-12 21:15:15 3361

原创 浅议C++回调函数的实现方式

引子-模板方法模式的实现看一下设计模式中模板方法模式的实现方式,首先定义一个模板基类,它有一个模板成员函数和一个钩子成员函数:class TemplateBase { int data; // 假设有一个int型的数据成员public: virtual ~TemplateBase() {} void process() { // 模板函数 // 其它准备操作 hook(); // 其它后续操作 } virtual void hook() {} // 钩子函数 // 其它

2021-11-30 09:34:47 1626 1

原创 智能指针之unique_ptr

unique_ptr用于独占它所指向的对象。某个时刻只能有一个unique_ptr指向一个给定的对象,也就是这个对象不会被多个unique_ptr同时共享,它只提供了移动语义,即它所管理的资源对象只能在unique_ptr之间进行移动,不能拷贝。资源对象的生命周期被唯一的一个unique_ptr托管着,一旦unique_ptr被销毁或者变成empty对象,或者拥有了另一个资源对象,它先前拥有的对象同时一并销毁,显然一旦对象离开unique_ptr的管理范围就会销毁,保证了内存不会泄露。下面看一下它的用法。

2021-11-12 16:45:25 14170 1

原创 构造函数和析构函数中调用虚函数是多态吗

在面向对象编程语言中,一个类的构造函数和析构函数是两个比较特殊的成员函数,它们主要用于对象的创建和销毁,和对象的生命周期息息相关,因此它们有着特殊的含义。编译器对待它们和其它普通的成员函数不一样,在编译它们时会添加一些额外的代码来做一些专门用途的业务逻辑。本文介绍其中一个和虚函数调用有关的场景及其实现机制。我们知道,C++为了实现面向对象的多态语义,设计了虚函数机制,具体地说,就是每个带有虚函数的类都会有一个虚函数表vtbl,在里面存放了各个虚函数的调用入口地址,在创建对象时,会为对象分配一个虚函数指针v

2021-11-09 16:50:59 855

原创 自旋锁的实现及优化

自旋锁的实现算法大多使用的是Test And Set算法,简称TAS,也就是先对目标值进行检查,如果目标符合预期的要求则同时把它修改为所需要的值。先介绍一下TAS原语,它的语义原型如下:function tas(p : pointer to bool) returns bool { bool value = *p if !*p { *p ←true } return value}它的语义是这样的:如果指针p指向的变量原值为false,就设置

2021-11-04 17:17:30 5087 3

原创 双重检查锁与单例模式

单例模式是比较常见的一种设计模式,它的实现方式有很多种,曾经见过一篇文章中列了十几种实现方式,比如饿汉式、懒汉式、双重检查锁、枚举。。。等等,大家也应该非常熟悉常见的实现方式,本文简单的谈谈其中的“双重检查锁”实现方式。我们知道单例模式的对象在进程中仅有一份,在多线程环境下为了防止创建出多个对象,需要对创建对象的过程进行互斥操作,这样,当多线程同时竞争时,保证只能由一个线程来创建唯一对象。互斥操作方式常见的就是锁,比如互斥锁或者自旋锁。下面的C++代码片段就是使用mutex锁来实现的,原理大家都明白,就不

2021-10-26 18:38:09 2503

原创 Android中的设计模式-职责链模式

在介绍该模式之前,先提一个问题,下图是SQL语句select执行时结果集的流转图,如果让你编写解析该select语句的代码,你会怎样设计方案呢?因为在select语句中,有些字段不一定出现,如group、limit、where等,因此,肯定不能按照固定的语句格式来解析,最常见的方式可能就是,使用大量的if。。else if。。else if。。else。。语句,根据select中实际出现的字段去调...

2018-05-22 13:38:55 475

空空如也

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除