c++多线程move是如何实现销毁原对象的?

  • unique_ptr 独享所有权(单个智能指针指向一块内存)
  • shared_ptr共享所有权(多个智能指针指向同一块内存)
创建类型为T的智能指针
创建类型为T的智能指针(C++14)

使用智能指针之后,就可以不用关心delete的时机,这些都由智能指针帮忙释放。

重置:会delete所管理的指针
释放:返回被管理的指针,智能指针置空
转移被管理的指针给其他unique_ptr,本身置空
获取被管理的指针,智能指针仍在管理

 
 


 

注意:不能使用智能指针来接受智能指针的释放

范例4:转移智能指针的所有权

该方法就是可以解决范例3中的报错情况。


范例5:获取智能指针管理的指针


 

注意:不能使用智能指针来接受智能指针的获取(智能指针在初始化时不能用其他智能指针的get()来初始化)

类中包含智能指针的情况

不难看出,当TESTPTR对象a被exe回收的时候,也会把TESTPTR_B的对象析构,防止内存泄露。

注意:智能指针unqiue_ptr中途中被get()后,被管理指针(iprt)在其他地方被delete,那么在unqiue_ptr就需要release被管理的指针(iprt),不然unqiue_ptr在被回收时会再次delete 被管理的指针(iprt)从而造成程序崩溃。

  • 当指针交给智能指针管理之后,如果想要delete,就调用unqiue_ptr的reset就好

  • unqiue_ptr不能通过拷贝构造生成,也不可用=运算符来赋值,只能通过移动来赋值。例子如下

有两块数据组成,一个是数据块(存放指针),一个是计数块(记录引用计数)

创建类型为T的智能指针
创建类型为T的智能指针(C++14)
返回被管理的指针的引用计数
智能指针重置后,重新管理新的指针

范例3:智能指针重置后,重新管理新的指针

智能指针在计数变为0的时候会调用析构函数的delete。但是管理数组指针的时候就有局限性,就需要使用自定义删除器。

  • 回调函数 (参数为智能指针管理的数据类型)

  • lambda表达式 (lambda表达式其实就是上面的中的另外一种写法罢了)

智能指针管理指针数组时

智能指针管理 含有指针的 STL容器

一般情况下,开发过程中如果要用到数组一般是使用STL的vector容器来代替数组,也可以交给智能指针管理。这里我用的是自定义数据类型。

使用函数对象作为自定义删除器

函数对象:自定义类型对()重载,也就是仿函数。注意仿函数和自定义的类要分开写,详情见下面遇到的问题。

  • 智能指针存放自定义数据类型数组
  • 智能指针存放自定义数据类型

问题描述:这两种情况使用自定义删除器,在创建智能指针的时候会额外构造对象!!虽然能正常释放……

eg1:智能指针中存放数组,数组存放自定义数据类型,使用函数对象作为自定义删除器来回收内存。

下图中红色方框都是问题所在

上述问题中主要的错误在于自定义数据类型和仿函数(函数对象)没有分开写,导致重载的()污染了原来的代码逻辑。

lambda:lambda是C++11的新特性,可以代替 函数或者函数对象

1、不能使用栈中之中来构造智能指针

应为栈中的数据在超出作用域之后就会被程序回收,然后智能指针也会对这块数据

2、不能用一个原始堆区数据来构造

}

        本文是记录平常看到的面试八股文问题,其问题主要是从leetcode中的讨论区整理出来的,答案大多是参考一些大佬讲解来整理的,在此进行整理,方便大家换工作时来熟悉下常见的八股文问题。大家可以看看,也可以讨论下:

(1)设计方案一:定长发送
(2)设计方案二:尾部标记序列
(3)设计方案三:头部标记分步接收


1、进程间通信的七种方式:

(1)管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
(2)命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
(3)消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
(4)共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
(5)信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
(6)套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
(7)信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

重要点:采用共享内存进行通信的一个主要好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝,对于像管道和消息队里等通信方式,则需要再内核和用户空间进行四次的数据拷贝(磁盘文件-用户空间-内核空间-用户空间-磁盘文件),
而共享内存则只拷贝两次:一次从输入文件到共享内存区,另一次从共享内存到输出文件。

一个进程中的两个线程间通信方式:
    互斥量可以保护共享数据的修改,如果线程正在等待共享数据的某个条件出现,仅用互斥量的话就需要反复对互斥对象锁定解锁,以检查值的变化,这样将频繁查询的效率非常低。
    条件变量可以让等待共享数据条件的线程进入休眠,并在条件达成时唤醒等待线程,提供一种更高效的线程同步方式。条件变量一般和互斥锁同时使用,提供一种更高效的线程同步方式。


3、进程上下文切换的细节

硬件中断和软中断的区别:
    硬件中断是由外设引发的, 软中断是执行中断指令产生的.
    硬件中断的中断号是由中断控制器提供的, 软中断的中断号由指令直接指出, 无需使用中断控制器.
    硬件中断处理程序要确保它能快速地完成任务, 这样程序执行时才不会等待较长时间, 称为上半部.
    软中断处理硬中断未完成的工作, 是一种推后执行的机制, 属于下半部.


5、自旋锁和互斥锁的区别

自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名。其作用是为了解决某项资源的互斥使用。因为自旋锁不会引起调用者睡眠,所以自旋锁的效率远 高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处:
    (1)、自旋锁一直占用CPU,他在未获得锁的情况下,一直运行--自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。

因此我们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。


自旋锁、互斥锁的应用:
    互斥锁用于临界区持锁时间比较长的操作,比如下面这些情况都可以考虑

    至于自旋锁就主要用在临界区持锁时间非常短且CPU资源不紧张的情况下,自旋锁一般用于多核的服务器。


6、死锁产生的四个条件

产生死锁的四个必要条件:

  ● 互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

  ● 请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

  ● 不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。

  ● 循环等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

(1) 、du : 显示每个文件和目录的磁盘使用空间~~~文件的大小。

(2)、 df:显示磁盘分区上可以使用的磁盘空间

(3)、free  可以显示Linux系统中空闲的、已用的物理内存及swap内存,及被内核使用的buffer。

8、僵尸进程和孤儿进程

    答:守护进程是后台运行的、系统启动是就存在的、不予任何终端关联的,用于处理一些系统级别任务的特殊进程。
(1)fork()创建子进程,父进程exit()退出;
(2)在子进程调用setsid()创建新会话;
(3)再次 fork() 一个子进程,父进程exit退出;
(4)在子进程中调用chdir()让根目录“/”成为子进程的工作目录;
(5)在子进程中调用umask()重设文件权限掩码为0;
(6)在子进程中close()不需要的文件描述符;
(7)守护进程退出处理

10、大端模式和小端模式

大端:将表示一个对象的字节在内存中按照从最高有效字节到最低有效字节的顺序存储,即最高有效字节在内存地址最前面的方式,称为大端法小端:将表示一个对象的字节在内存中按照从最低有效字节到最高有效字节的顺序存储,即最低有效字节在内存地址最前面的方式,称为小端法


返回值:大端返回1,小端返回0


12、虚拟内存的原理和优点

    虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,
    还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,

    (1).虚拟内存将主存看作是在磁盘地址空间上的高速缓存,主存中只保存活动区域并根据需要在磁盘和主存之间来回传送数据
    (2).虚拟内存保护每个进程的地址空间不被其他进程破坏
    (3).虚拟内存为进程提供了一致的地址空间简化了内存管理
虚拟内存有以下两个优点:

    (2).虚拟内存的最大空间就是cup的最大寻址空间,不受内存大小的限制,能提供比内存更大的地址空间
13、库函数调用和内核函数调用的区别
    (1)系统调用是最底层的应用,是面向硬件的。而库函数的调用是面向开发的,相当于应用程序的API(即预先定义好的函数)接口;

    (2)各个操作系统的系统调用是不同的,因此系统调用一般是没有跨操作系统的可移植性,而库函数的移植性良好(c库在Windows和Linux环境下都可以操作);

    (3)库函数属于过程调用,调用开销小;系统调用需要在用户空间和内核上下文环境切换,开销较大;

    (4)库函数调用函数库中的一段程序,这段程序最终还是通过系统调用来实现的;系统调用调用的是系统内核的服务。

    (1)内核指的是一个提供硬件抽象层、磁盘及文件系统控制、多任务等功能的系统软件。内核是一个操作系统的核心,是操作系统最基本的部分。它负责管理系统的进程、内存、设备驱动程序、
    文件和网络系统等,决定着系统的性能和稳定性。它是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并且内核决定一个程序在什么时候对某部分硬件
    操作多长时间。直接对硬件操作是非常复杂的,所以内核通常提供一种硬件抽象的方法来完成这些操作。硬件抽象隐藏了复杂性,为应用软件和硬件提供了一套简洁,
    统一的接口,使程序设计更为简单。一个内核不是一套完整的操作系统。比如一套基于Linux内核的完整操作系统叫作Linux操作系统,或是GNU/Linux。
    进程管理:内核负责创建和销毁进程, 并处理它们与外部世界的联系(输入和输出),不同进程间通讯(通过信号,管道,或者进程间通讯原语)对整个系统功能来说是基本的,也由内核处理。 另外, 调度器, 控制进程如何共享CPU,是进程管理的一部分。更通常地,内核的进程管理活动实现了多个进程在一个单个或者几个CPU 之上的抽象。         内存管理:计算机的内存是主要的资源, 处理它所用的策略对系统性能是至关重要的。内核为所有进程的每一个都在有限的可用资源上建立了一个虚拟地址空间。内核的不同部分与内存管理子系统通过一套函数调用交互,从简单的malloc/free对到更多更复杂的功能。        
    文件管理:Linux 在很大程度上基于文件系统的概念;几乎Linux中的任何东西都可看作一个文件。内核在非结构化的硬件之上建立了一个结构化的文件系统,结果是文件的抽象非常多地在整个系统中应用。另外,Linux 支持多个文件系统类型,就是说,物理介质上不同的数据组织方式。例如,磁盘可被格式化成标准Linux的ext3文件系统,普遍使用的FAT文件系统,或者其他几个文件系统。        
    驱动管理:几乎每个系统操作终都映射到一个物理设备上,除了处理器,内存和非常少的别的实体之外,全部中的任何设备控制操作都由特定于要寻址的设备相关的代码来进行。这些代码称为设备驱动。内核中必须嵌入系统中出现的每个外设的驱动,从硬盘驱动到键盘和磁带驱动器。        
    网络管理:网络必须由操作系统来管理,因为大部分网络操作不是特定于某一个进程: 进入系统的报文是异步事件。报文在某一个进程接手之前必须被收集,识别,分发,系统负责在程序和网络接口之间递送数据报文,它必须根据程序的网络活动来控制程序的执行。另外,所有的路由和地址解析问题都在内核中实现。
explicit可以抑制内置类型隐式转换,所以在类的构造函数中,最好尽可能多用explicit关键字,防止不必要的隐式转换

答:(1)volatile关键字的作用:保证了变量的可见性(visibility)。被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象


4、如何限制对象只能在堆上创建/如何限制对象只能在栈上创建?
(1)只在堆上创建编译器在为类对象分配栈空间时,会先检查类的析构函数的访问性,其实不光是析构函数,只要是非静态的函数,编译器都会进行检查。如果类的析构函数是私有的,则编译器不会在栈空间上为类对象分配内存


只有使用new运算符,对象才会建立在堆上,因此,只要禁用new运算符就可以实现类对象只能建立在栈上。将operator new()设为私有即可。代码如下:


5、如何创建出一个类不可以被继承


 6、单例模式和工厂模式——工厂模式稍微有点复杂
 7、指针和引用的区别
 8、如何实现泛型编程 —— 模板,容器实现
(1)auto使用的是模板实参推断(Template Argument Deduction)的机制。auto被一个虚构的模板类型参数T替代,然后进行推断,即相当于把变量设为一个函数参数,将其传递给模板并推断为实参,
 auto相当于利用了其中进行的实参推断,承担了模板参数T的作用
(2)而auto类型变量不会是引用类型(模板实参推断的规则),所以要用auto&(C++14支持直接用decltype(auto)推断原始类型),第二个auto推断对应于下面这个模板传参时的情形,
同样T就是为auto推断的类型
(3)唯一例外的是对初始化列表的推断,auto会将其视为std::initializer_list,而模板则不能对其推断

(2)constexpr修饰的函数,简单的来说,如果其传入的参数可以在编译时期计算出来,那么这个函数就会产生编译时期的值。但是,传入的参数如果不能在编译时期计算出来,
那么constexpr修饰的函数就和普通函数一样了。不过,我们不必因此而写两个版本,所以如果函数体适用于constexpr函数的条件,可以尽量加上constexpr。
而检测constexpr函数是否产生编译时期值的方法很简单,就是利用std::array需要编译期常值才能编译通过的小技巧。
这样的话,即可检测你所写的函数是否真的产生编译期常值了。

13、https中客户端为什么信任 证书?
(1)客户端得到服务端返回的证书,通过读取得到 服务端证书的发布机构(Issuer)
(2)客户端去操作系统查找这个发布机构的的证书,如果是不是根证书就继续递归下去 直到拿到根证书。
(3)用 根证书的公钥 去 解密验证 上一层证书的合法性,再拿上一层证书的公钥去验证更上层证书的合法性;递归回溯。
(4)最后验证服务器端的证书是 可信任 的。

14、http中 长连接和短链接的应用场景?

    1)长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,
    所以每个操作完后都不断开,次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,

    2)像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,
    而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。

(2)长连接与短连接区别:

    长连接:连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接(一个TCP连接通道多个读写通信); 

15、ARP攻击和防护方法


16、对称加密和非对称加密


(1)对称加密是指加密和解密都使用同一个秘钥; 而非对称加密是有公钥和私钥两个秘钥的.
    对称加密速度相对更快,但安全性较低,如果一方的秘钥泄露,那密文就相当于明码了.

18、当map中的key值 为结构体时,需要重载<号

}

我要回帖

更多关于 多线程调用同一个对象的方法 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信