首页 > 编程语言 >C++面试八股文:什么是智能指针?

C++面试八股文:什么是智能指针?

时间:2023-06-19 23:11:36浏览次数:44  
标签:std 八股文 C++ 智能 shared ptr 师兄 指针

某日二师兄参加XXX科技公司的C++工程师开发岗位第19面:

面试官:什么是智能指针?

二师兄:智能指针是C++11引入的类模板,用于管理资源,行为类似于指针,但不需要手动申请、释放资源,所以称为智能指针。

面试官:C++11引入了哪些智能指针?

二师兄:三种,分别是shared_ptrunique_ptr、和weak_ptr

面试官:说一说三种指针的特征及用途。

二师兄:好的。shared_ptr使用了引用计数(use count)技术,当复制个shared_ptr对象时,被管理的资源并没有被复制,而是增加了引用计数。当析构一个shared_ptr对象时,也不会直接释放被管理的的资源,而是将引用计数减一。当引用计数为0时,才会真正的释放资源。shared_ptr可以方便的共享资源而不必创建多个资源。

file

二师兄:unique_ptr则不同。unique_ptr独占资源,不能拷贝,只能移动。移动过后的unique_ptr实例不再占有资源。当unique_ptr被析构时,会释放所持有的资源。

file

二师兄:weak_ptr可以解决shared_ptr所持有的资源循环引用问题。weak_ptr在指向shared_ptr时,并不会增加shared_ptr的引用计数。所以weak_ptr并不知道shared_ptr所持有的资源是否已经被释放。这就要求在使用weak_ptr获取shared_ptr时需要判断shared_ptr是否有效。

struct Boo;
struct Foo{
    std::shared_ptr<Boo> boo;
};
struct Boo{
    std::shared_ptr<Foo> foo;
};

二师兄:Foo中有一个智能指针指向Goo,而Goo中也有一根智能指针指向Foo,这就是循环引用,我们可以使用weak_ptr来解决这个文通。

Boo boo;
auto foo = boo.foo.lock();
if(foo)
{
    //这里通过获取到了foo,可以使用
}else
{
    //这里没有获取到,不能使用
}

面试官:好的。智能指针是线程安全的吗?

二师兄:是的。抛开类型T,智能指针是类型安全的。

面试官:为什么?

二师兄:因为智能指针底层使用的引用计数是atomic的原子变量,原子变量在自增自减时是线程安全的,这保证了多线程读写智能指针时是安全的。

面试官:好的。为什么尽量不要使用裸指针初始化智能指针?

二师兄:因为可能存在同一个裸指针初始了多个智能指针,在智能指针析构时会造成资源的多次释放。

面试官:为什么不要从智能指针中返回裸指针呢?

二师兄:是因为如果返回的裸指针被释放了,智能指针持有的资源也失效了,对智能指针的操作是未定义的行为。

面试官:智能指针能够持有数组吗?

二师兄:shread_ptrunique_ptr都可以持有数组。

面试官:那你知道在释放资源的时候两者有什么不同吗?

二师兄:这个暂时还不清楚。。

面试官:可以使用静态对象初始化智能指针吗?

二师兄:让我想想。。不可以,因为静态对象的生命周期和进程一样长,而智能指针的析构的时候会导致静态资源被释放。这会导致未定义的行为。

面试官:如果需要在一个类中实现一个方法,这个方法返回这个类的shread_ptr实例,需要注意哪些东西?

二师兄:需要继承std::enable_shared_from_this类,方法返回shared_from_this()

struct Foo : public std::enable_shared_from_this<Foo>
{
    std::shared_ptr<Foo> get_foo()
    {
        return shared_from_this();
    }
};

面试官:为什么不直接返回this指针?

二师兄:额。。。不太清楚,但是这应该是个范式。

面试官:好的,今天的面试结束了,请回去等通知吧。

今天二师兄的表现不错,让我们看看一些回答的不太理想的地方吧。

智能指针是线程安全的吗?

很遗憾,使用不当的时候并不是。

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>

struct Foo
{
    Foo(int i):i_(i){}
    void print() {std::cout << i_ << std::endl;}
    int i_;
};

int main(int argc, char const *argv[])
{
    {
        auto shptr = std::make_shared<Foo>(42);
        std::thread([&shptr](){
            std::this_thread::sleep_for(std::chrono::seconds(1));
            shptr->print();
        }).detach();
    }
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 0;
}
// g++ test.cpp -o test -lpthread
// ./test 
// Segmentation fault

当我们向另一个线程传递智能指针的引用时,由于use count并没有加1,在shptr析构时直接销毁了管理的Foo实例,所以在线程中执行shptr->print()会引发coredump

修改起来也很简单,把std::thread([&shptr]()改成std::thread([shptr]()即可。记住,智能指针尽量不要传引用

知道在释放资源的时候shread_ptrunique_ptr有什么不同吗?

这里需要在shared_ptr构造时传入deleter,用来销毁持有的数组,而unique_ptr无需此操作,因为unique_ptr重载了unique_ptr(T[])

get_foo()方法为什么不直接返回this指针?

参考 ”为什么尽量不要使用裸指针初始化智能指针“。聪明的小伙伴,想想如果多次调用get_foo()会发生什么?

好了,今天二师兄的面试之旅到这里就结束了。感谢小伙伴的耐心阅读。如果您觉得还不错,请多多支持二师兄,拜谢~

关注我,带你21天“精通”C++!(狗头)

标签:std,八股文,C++,智能,shared,ptr,师兄,指针
From: https://www.cnblogs.com/binarch/p/17492470.html

相关文章

  • C++继承和派生
    #继承和派生在C++中,继承和派生是面向对象编程的两个重要概念,用于实现类与类之间的关系。继承是指一个类可以从另一个类中继承属性和方法,并且可以在此基础上扩展出自己的属性和方法。被继承的类称为基类(父类),继承的类称为派生类(子类)。在C++中,可以通过以下方式定义一个派生类:```c++cl......
  • 内核启动阶段获得dtb位置指针过程
    一.内核启动阶段获得dtb位置指针以arm64为例,内核启动如下:/arch/arm64/kernel/head.S __HEAD_head: /* *DONOTMODIFY.ImageheaderexpectedbyLinuxboot-loaders. */#ifdefCONFIG_EFI /* *Thisaddinstructionhasnomeaningfuleffectexceptthat *its......
  • Git基础、Git指针、Git分支、Git标签
    什么是Git:它是一个分布式版本控制系统,支持断网操作,每个开发者都是一个仓库的完整克隆,每个人都是服务器         为什么要使用Git:它可以有效、高速的处理从很小到非常大的项目版本控制1.Git安装:   yum-yinstallgit  #使用yum安装Git           ......
  • c++ 2.0 总结
    class内存分配与释放#include<iostream>#include<memory>usingnamespacestd;classPerson{public:Person(){cout<<"personconstructor"<<endl;}~Person(){cout<<"person......
  • C++:STL库
    模板编程泛型编程STL常用组件lambda表达式异常处理内存处理部分数据结构部分算法STL由算法,容器,迭代器,适配器,仿函数(函数对象),空间适配器六大部件组成。我们将主要讲解容器,迭代器,算法和仿函数。适配器的部分会交给学员来实现,而空间适配器不会太过于深入。从上往下学习STL,学习曲......
  • C++11:多线程
    传统的C++(C++11之前)中并没有引入线程这个概念C++11引入了头文件<thread>,提供了管理线程保护共享数据线程间同步操作原子操作等  <thread>join()detach()get_id()yield()sleep_for()sleep_until() #include<thread>intmain(){ std::threadt......
  • C++ typeid关键字详解
    typeid关键字 注意:typeid是操作符,不是函数。这点与sizeof类似)运行时获知变量类型名称,可以使用typeid(变量).name()需要注意不是所有编译器都输出”int”、”float”等之类的名称,对于这类的编译器可以这样使用intia=3;if(typeid(ia)==typeid(int)){  cout<<"in......
  • C++11:Lambda表达式
    Lambda表达式为了一些简单的函数直接调用封装[var]:表示值传递方式捕捉变量var[=]:表示值传递捕捉所有父作用域中的变量(包括成员函数中的this)[&var]:表示引用传递捕捉变量var[&]:表示引用传递捕捉所有父作用域中的变量(包括成员函数中的this)[this]:表示值传递方式捕捉当前的this指......
  • UE5 C++ 定时器使用
    概念定时器在全局定时器管理器(FTimerManager类型)中管理。全局定时器管理器存在于游戏实例对象上以及每个场景中。有两个函数可以使用定时器管理器来设置定时器:SetTimer和SetTimerForNextTick,它们各自都有一些重载,每个函数都可以连接到任意类型的对象或函数委托使用访......
  • 记录一次Java Convert Kotlin造成的空指针异常
    不知道大家在使用Kotlin进行编码的时候,有没有直接使用AS的Code->ConvertJavaFile2KotlinFile这个功能,此功能在日常使用中还是比较实用的,可以帮助我们将老的Java或者复制的Java代码一键转换成Kotlin代码,最近在使用此功能的时候竟然遇到了空指针的Crash,在此记录一下,顺便也给大......