美文网首页
C++智能指针/普通指针的一些比较

C++智能指针/普通指针的一些比较

作者: 陈瓜瓜_ARPG | 来源:发表于2019-10-02 10:29 被阅读0次

其实想写这个有一段时间了,可能好多写C++许久的人都不敢说自己精通C++指针,因为这里面水很深。自C++11后智能指针的问世更是把指针的使用升了一个层次。根据自己最近的一些应用,想把智能指针和普通指针作个对比,以供后人学习。这里的比较主要是比较指针之间相互赋值所带来的一些问题

把一个指针赋值给另一个指针

首先是普通指针,大家来看一下下面的这个程序,复习一下普通指针

    int x;
    int y;
    int *p = &x;
    int *q = &y;

    x = 35;
    y = 46;

    p = q;      // at this point p is now pointing to the same memory address 
                // as q, both of them are pointing to the memory allocated to y

    *p = 90;    // this will also change the values in *q and y 

    cout << x << " " << y << endl;
    cout << *p << " " << *q << endl;

    return 0;

猜猜看输出结果是什么呢?输出结果是

35 90
90 90

有一个比较有意思的地方是,*q的值改变了,本来之前应该是x即35而现在是90.可以看到我们程序开始把y的地址赋值给了q*q就应该是46了。之后我们没有再对q赋值而是把q赋值给了p。最后我们改变了*p的值没想到也改变了*q的值。原来当程序走过p = q后,pq都指向相同的地址了,改变p指向的值就是改变q指向的值。这个简单的问题竟然在stackoverflow里有了数万的浏览了,见链接
https://stackoverflow.com/questions/7062853/c-pointer-assignment/7062888

使用new初始化普通指针?不要使用new

我想在早期的C++教材或者程序或多或少能看到new的身影,必须要谈及之前的人们为什么要使用它。网上有很多答案,你看了之后觉得懂了,但很可能其实你不懂。最普遍的答案比如new可以让你手动管理内存,使用new初始化了指针之后必须要用delete才能删除指针以及指针指向的内容(大家对边搜为什么要使用new之类的就可以看到类似的答案)。比如下面这个简单的程序

int a = 5;
{
int *p;
int *b = new int;
p = &a;
b = &a;
}

定义了一个普通指针p以及new定义了一个指针b。作为一个局部指针,p在出了大括号{}后就自动消失了。但是b不然,b作为一个new开辟了内存空间的指针,在出了{}之后,它开辟的内存空间(大小为一个int所占的字节)是仍然存在的,如果没有delete,那块内存空间就会一直放在那儿,甚至在程序结束之后都在那儿,一直不能使用了,这就是所谓内存泄漏
但是!!!!!!!
在出了局部{}之后,指针b指向的内容虽然存在,但是指针b已经不可使用了!它的作用域只在{}内。比如你在括号外使用b

int a = 5;
{
int *p;
int *b = new int;
p = &a;
b = &a;
}
std::cout<<*b<<std::endl;//Wrong. b is not defined

程序会告诉你b不存在,没有定义。b相当于就是一个局部指针,它只能在局部范围使用,虽然它开辟的内存空间还在,但是它已经不在了(听起来这么别扭)....你想要在括号外delete它也是不行的。

int a = 5;
{
int *p;
int *b = new int;
p = &a;
b = &a;
}
delete b;//Wrong. b is not defined

当前情况下(后面会讲其他情况),你必须在这个局部范围内把它给delete了。

int a = 5;
{
int *p;
int *b = new int;
p = &a;
b = &a;
delete b;//Correct
}

那么问题来了,既然专门开辟了内存空间的指针也只能作为局部的指针使用,那么我TM为什么要用它???在这个局部范围之内,p指针的作用和它一模一样,出了局部范围,两个指针都不能使用。b指针还必须在局部范围内手动删除,那么我TM为什么要用它???
百度一下关于new的知识,甚至会有错误答案,比如下面这个链接
https://blog.csdn.net/qq_18884827/article/details/52334303
这是我在搜索c++什么时候使用new时的第一个链接,但是很遗憾里面的程序是错误的。它大概说使用了new,指针就可以全局使用了,刚才我们已经说了不可以。它举的例子如下

void func()
{
Student *st=new Student;
st->name="hello";
}
void main()
{
func();
cout<<st->name;//原博主说的`正确的调用`,其实错误
}

但是你随便写一个Student类来补充完整程序,在main函数中调用st程序同样会告诉你st不存在。
但是下面这种情况下,new定义的指针就可以在函数外使用了,我们把程序做了微调并补充完整

class Student{
public:
    std::string name;
};

Student* PrintName(){
    Student *st = new Student;
    st->name = std::string("ABC");
    return st;  //return the pointer
}
int main(){
    Student* st = PrintName();
    std::cout<<st->name<<std::endl;
    delete st;
}

运行这个程序你会发现你可以得到输出ABC。也就是说可以把new定义的指针作为值传递出去。而普通的指针是不行的。

class Student{
public:
    std::string name;
};

Student* PrintName(){
    Student *st; 
    st->name = std::string("ABC");
    return st;  //return the pointer
}
int main(){
    Student* st = PrintName();
    std::cout<<st->name<<std::endl;
}

运行上面这个程序,会出现段错误。new定义的指针确实可以应用到更广的范围,但需要我们上面的程序那样把它"传递"出去。这TM才是我们为什么要用它,并不是可以直接当全局变量。
第二个要用它的原因,很多文章都说了,new是定义了动态分配的内存。比如下面这个百度的回答
https://zhidao.baidu.com/question/553749984.html
它说而一般直接声明数组时,数组大小必须是常量。其实不然。下面这个函数

void func(int i){
     int p[i];
}

是可行的,你可以随时传递一个i进去。也就是在传递进去一个i之前这个数组大小不定。下面这个函数

void func(int i){
     int* p[i];
     //*p[0] = 0;
}

也是可行的。数组指针p的要指的数组的大小一开始也不确定。传入了i之后可以再给指针指向的地址赋值。
所以百度上错误答案或者get不到点的答案还挺多的。我也去StackOverflow上之类的查了一下,很多回答比如
https://stackoverflow.com/questions/655065/when-should-i-use-the-new-keyword-in-c
也切不到关键点。
还有一点我们要注意的是,一旦delete了new定义的指针,它指向的东西也会被销毁。下面的代码可以一目了然

    int a = 5;
    {
    int *p;
    int *b = new int;
    p = &a;
    b = &a;
    delete b;
    }
    std::cout<<a<<std::endl;

运行这个程序,你会得到segmentation fault之类的错误。因为delete b的同时,a也没了,自然不能cout了。
关于new的第一个要使用的原因,其实也有很多代替品。智能指针出现后更是了。所以我们的总结再次强调,C++11后,没有用智能指针的话
不要使用new, 不要使用new, 不要使用new!!!

智能指针赋值

智能指针,自问世以后大量被使用。目前常用的有shared_ptrunique_ptr以及weak_ptr。前面两个使用最多,他们的定义方式如下(整型指针举例)

    std::unique_ptr<int> ptr0 = std::make_unique<int>(); //C++14 以及之后才可以
    std::shared_ptr<int> ptr1 = std::make_shared<int>();//C++11可以
 //or
    std::unique_ptr<int> ptr0(new int);
    std::unique_ptr<int> ptr1(new int);

尽量使用第一种方法,这也是C++官方推荐的。如果要初始化

    std::unique_ptr<int> ptr0 = std::make_unique<int>(5); //C++14 以及之后才可以
    std::shared_ptr<int> ptr1 = std::make_shared<int>(5);//C++11可以
 //or
    std::unique_ptr<int> ptr0(new int(5));
    std::unique_ptr<int> ptr1(new int(5));

这样指针指向的值就是5。weak_ptr的构造方法有些不同,它必须从shared_ptr来创建,有兴趣的同学自己查一下,我也没用过这个指针。
先说一下我使用得最多的shared_ptr。顾名思义,这个指针是可以共享的。所谓共享,就是如果你有两个指针如下

std::shared_ptr<int> ptr0 = std::make_shared<int>();
std::shared_ptr<int> ptr1 = std::make_shared<int>();
int a = 5;
*ptr0 = a; //ptr0 = &a is wrong!!!
ptr1 = ptr0;//share

最后一行使得ptr1和ptr0共享资源ashared_ptr使用计数机制来表明资源被几个指针共享。当我们调用release()时,当前指针会释放资源所有权,计数减1。当计数等于0时,资源会被释放。注意我的注释,虽然仍然是指针,给地址的方法是无法给shared_ptr赋值的。大家可能一时半会儿不能体会到这种共享的强大。我给大家举个简单的栗子。

    std::shared_ptr<int> ptr0 = std::make_shared<int>();
    {
        std::shared_ptr<int> ptr1 = std::make_shared<int>();
        int a = 5;
        *ptr1 = a;
        ptr0 = ptr1;
    }
    std::cout<<*ptr0<<std::endl;

还记得我们之前说的我们简单的使用int* p = new int的这种初始化指针的方法,如果初始化在局部范围,根本不能在局部外使用p。而有了shared_ptr,这个小愿望就可以达成了。上面的程序,我们在局部{}内定义了智能指针ptr1。并把它赋值给了ptr0,此时共享指针计数为2。在局部范围之外,虽然ptr1消失了,a也消失了,此时共享指针的计数为1,但我们仍然可以通过*ptr0获取到之前a储存的值,也就是5。所以我们cout最后能得到5。这就好像,a的肉身(括号之外std::cout<<"a"<<std::endl是不行的)消失了,但是a的灵魂还在(你仍然可以获得5)。这才像"全局"的样子!
即使你new了一个全局的指针和一个局部指针,普通的new能这样吗?不能的。

    int* ptr0 = new int;
    {
        int* ptr1 = new int
        int a = 5;
        ptr1 = &a;
        ptr0 = ptr1;
        delete ptr1; //you have to delete ptr1 here or there will be memory leak!
                          //But then `a` will disappear
    }
    std::cout<<*ptr0<<std::endl;
   delete p1;

正如我注释里所说,你必须在局部把ptr1 delete了。不然就就内存泄露了,但是一旦delete 了ptr1,a就没了,灵魂和肉身都没了...
可能你又想,这有什么用??我遇到过一些情况,我在局部范围内获得了一些点云。不知道点云的朋友没关系,总是就是很大的数据量,而表示点云的库一般大量使用指针/智能指针去表示数据,因为大的数据最好通过指针获得,防止拷贝消耗内存时间等。要把局部的点云传到局部之外,shared_ptr再好用不过了。
unique_ptr其实没什么特别好说的,它作为指针的用处和shared_ptr基本一样,但是顾名思义,是"unique"的。不可拷贝复制。在某些特殊情况下你不希望自己的指针赋值给其他指针,就要用unique_ptr。下面这个程序会编译失败

    std::unique_ptr<int> ptr0 = std::make_unique<int>(5) ;
    std::unique_ptr<int> ptr1 = std::make_unique<int>() ;

    ptr1 = ptr0;//unique_ptr cannot be copied!!!

相关文章

  • Android智能指针分析

    Android智能指针分析总结 什么是智能指针 C++ 指针需要手动释放,否则会造成内存泄露,但是如果项目工程比较...

  • C++智能指针/普通指针的一些比较

    其实想写这个有一段时间了,可能好多写C++许久的人都不敢说自己精通C++指针,因为这里面水很深。自C++11后智能...

  • C++研发工程师笔试题/面试题(1-10)

    1. (1) 简述智能指针的原理;(2)c++中常用的智能指针有哪些?(3)实现一个简单的智能指针。 简述智能指针...

  • C++ 引用计数技术及智能指针的简单实现

    1.智能指针是什么 简单来说,智能指针是一个类,它对普通指针进行封装,使智能指针类对象具有普通指针类型一样的操作。...

  • 一文说尽C++智能指针的点点滴滴

    0、摘要 本文先讲了智能指针存在之前C++面临的窘境,并顺理成章地引出利用RAII技术封装普通指针从而诞生了智能指...

  • 五点讲述C++智能指针的点点滴滴

    0、摘要 本文先讲了智能指针存在之前C++面临的窘境,并顺理成章地引出利用RAII技术封装普通指针从而诞生了智能指...

  • C++ 智能指针

    C++智能指针[https://zhuanlan.zhihu.com/p/54078587] C++11中智能指针...

  • c++智能指针用法

    智能指针是什么 智能指针是c++中有四个智能指针:auto_ptr、shared_ptr、weak_ptr、uni...

  • 智能指针

    指针的危害 指针未初始化 野指针 内存泄漏 参考阅读C/C++指针使用常见的坑 智能指针分类 本质:将指针封装为类...

  • c++中类的成员函数指针

      在c++中,使用函数指针的时候,我一般使用静态成员函数的指针。另外,还有一种普通成员函数的指针,我用的比较少。...

网友评论

      本文标题:C++智能指针/普通指针的一些比较

      本文链接:https://www.haomeiwen.com/subject/ueulpctx.html