C++11----智能指针

本文深入探讨了C++中的智能指针概念及其在解决内存泄露问题中的应用,包括auto_ptr、scoped_ptr、unique_ptr、shared_ptr及weak_ptr等类型的特点与使用场景,并特别关注它们在多线程环境下的表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++11智能指针和引用计数

你有什么办法解决资源泄露(内存泄露)?


引入智能指针:
原理: 首先它是一个指针,对裸指针(带*号的指针)用面向对象的类进行封装,所以能指向内存资源;然后它区别于一般的指针,它是智能的,而所谓的智能是指它:
1.能够自动的帮你释放内存
2.还能够知道什么时候释放内存才是安全的。


C++中智能指针的实现主要依赖于两个技术概念:RAII技术


1. 析构函数:

对象被销毁时会被调用的一个函数,对于基于栈的对象而言,如果对象离开其作用域则对象会被自动销毁,而此时析构函数也自动会被调用。


2. 引用计数技术:


引用计数这种计数是为了防止内存泄露而产生的。基本想法是对于动态分配的对象,进行引用计数,每当增加一次对同一个对象的引用,那么引用对象的引用计数就会增加一次,每删除一次引用,引用计数就会减一,当一个对象的引用计数减为零时,就自动删除指向的堆内存。


注意:

引用计数不是垃圾回收,引用技术能够尽快收回不再被使用的对象,同时在回收的过程中也不会造成长时间的等待,更能够清晰明确的表明资源的生命周期。


不带引用计数的指针========================


auto_ptr 


c++标准库,低版本VS没有加入到标准库。不建议使用该智能指针,C++11已结弃用该关键字。


1.处理浅拷贝的方式,源码实现:
拷贝构造的时候:先保存之前的指针,再把之前的指针置空,让新对象的指针指向堆,只让最后的指针指向堆资源。
注意:不能在容器中使用auto_ptr ,容器操作涉及许多拷贝构造,不允许浅拷贝。
我们知道auto_ptr通过复制构造或者通过=赋值后,原来的auto_ptr对象就报废了.所有权转移到新的对象中去了。这个过程可以进行,但是开发者需要自己注意原来的智能指针对象失效了,这个过程是对开发者隐藏的。






scoped_ptr


来源boost库,低版本的C/C++标准库没有
源码上将拷贝构造和赋值函数都设置为private了

不能拷贝构造,也不能赋值,这样就不用考虑浅拷贝的问题。


unique_ptr


来源boost,低版本的C/C++标准库没有

1.怎么解决浅拷贝的?为什么可以用在容器中


和auto_ptr非常类似,都是让最后一个智能指针指向资源,之前的指针置空。并且和auto_ptr有三点不同
一:可以间接作为容器元素
例如:
unique_ptr<int> sp(new int(88) );
vector<unique_ptr<int> > vec;
vec.push_back(std::move(sp));
//vec.push_back( sp ); 这样不行,会报错的.
//cout<<*sp<<endl;但这个也同样出错,说明sp添加到容器中之后,它自身报废了.


二:可以进行移动构造和移动赋值操作。

既然是独占,换句话说就是不可复制。但是,我们可以利用 std::move 将其转移给其他的 unique_ptr,

例如:

#include <iostream>
#include <memory>


struct Foo {
    Foo()      { std::cout << "Foo::Foo" << std::endl;  }
    ~Foo()     { std::cout << "Foo::~Foo" << std::endl; }
    void foo() { std::cout << "Foo::foo" << std::endl;  }
};


void f(const Foo &) {
    std::cout << "f(const Foo&)" << std::endl;
}


int main() {
    std::unique_ptr<Foo> p1(std::make_unique<Foo>());


    // p1 不空, 输出
    if (p1) p1->foo();
    {
        std::unique_ptr<Foo> p2(std::move(p1));


        // p2 不空, 输出
        f(*p2);


        // p2 不空, 输出
        if(p2) p2->foo();


        // p1 为空, 无输出
        if(p1) p1->foo();


        p1 = std::move(p2);


        // p2 为空, 无输出
        if(p2) p2->foo();
        std::cout << "p2 被销毁" << std::endl;
    }
    // p1 不空, 输出
    if (p1) p1->foo();


    // Foo 的实例会在离开作用域时被销毁
}


三:无法进行复制构造与赋值操作。

auto_ptr<int> ap(new int(88 );
      auto_ptr<int> one (ap) ; // ok
      auto_ptr<int> two = one; //ok
//但unique_ptr不支持上述操作
  unique_ptr<int> ap(new int(88 );
  unique_ptr<int> one (ap) ; // 会出错
  unique_ptr<int> two = one; //会出错

2.为什么有两个模板参数,前面两个只有一个模板参数。

        一个只能指针类型一个删除器仿函数/函数对象,智能指针的删除器,对资源的释放行为进行定制,比如说文件需要close()

3. 使用示例
std::unique_ptr<int> pointer = std::make_unique<int>(10);   // make_unique 从 C++14 引入
std::unique_ptr<int> pointer2 = pointer;    // 非法
make_unique 并不复杂,C++11 没有提供 std::make_unique,可以自行实现:
template<typename T, typename ...Args>
std::unique_ptr<T> make_unique( Args&& ...args ) {
    return std::unique_ptr<T>( new T( std::forward<Args>(args)... ) );
}



带引用计数的智能指针=====================


shared_ptr强智能指针


可以改变资源的引用计数,对每一个资源都有一个引用标记,每当有指针引用该内存时都通过该引用计数的加减来实现对资源控制的技术。其中shared_ptr对引用计数的加减是一个原子操作,是CAS无锁的的原子操作,保证了线程安全。

但还不够,因为使用 std::shared_ptr 仍然需要使用 new 来调用,这使得代码出现了某种程度上的不对称。于是在编码中尽量不使用new,使用make_shared,函数模板。

std::make_shared 就能够用来消除显示的使用 new,所以std::make_shared 会分配创建传入参数中的对象,并返回这个对象类型的std::shared_ptr指针。


示例:
#include <iostream>
#include <memory>


void foo(std::shared_ptr<int> i)
{
    (*i)++;
}
int main()
{
    // auto pointer = new int(10); // 非法, 不允许直接赋值
    // 构造了一个 std::shared_ptr
    auto pointer = std::make_shared<int>(10);
    foo(pointer);
    std::cout << *pointer << std::endl; // 11


    // 离开作用域前,shared_ptr 会被析构,从而释放内存
    return 0;
}


std::shared_ptr 可以通过 get() 方法来获取原始指针,通过 reset() 来减少一个引用计数,并通过get_count()来查看一个对象的引用计数。例如:


auto pointer = std::make_shared<int>(10);
auto pointer2 = pointer;    // 引用计数+1
auto pointer3 = pointer;    // 引用计数+1
int *p = pointer.get();             // 这样不会增加引用计数


std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl;      // 3
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl;    // 3
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl;    // 3


pointer2.reset();
std::cout << "reset pointer2:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl;      // 2
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl;    // 0, pointer2 已 reset
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl;    // 2


pointer3.reset();
std::cout << "reset pointer3:" << std::endl;
std::cout << "pointer.use_count() = " << pointer.use_count() << std::endl;      // 1
std::cout << "pointer2.use_count() = " << pointer2.use_count() << std::endl;    // 0
std::cout << "pointer3.use_count() = " << pointer3.use_count() << std::endl;    // 0, pointer3 已 reset
weak_ptr弱智能指针

weak_ptr只能观察对象存活与否,不会引起对象的资源引用计数的改变,相当于一个观察者模式。不会引起引用计数的改变。


支持的转换关系:weak_ptr  ==>shared_ptr ====>对象


智能指针的两个问题:


一:智能指针的交叉引用/循环引用问题


错误代码:
#include <iostream>
#include <memory>


class A;
class B;


class A {
public:
    std::shared_ptr<B> pointer;
    ~A() {
        std::cout << "A 被销毁" << std::endl;
    }
};
class B {
public:
    std::shared_ptr<A> pointer;
    ~B() {
        std::cout << "B 被销毁" << std::endl;
    }
};
int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->pointer = b;
    b->pointer = a;


    return 0;
} 




场景:




解决方法:引入弱智能指针weak_ptr


原因是:

弱智能指针只是一个观察者,不会改变引用计数。弱智能指针没有重载*和->运算符的重载方法,但是不能通过弱智能指针去访问对象。
正确使用引用计数的智能指针的规则;
定义一个对象的时候用强智能指针去引用该对象。
在声明或者是类里面声明的时候用弱智能指针。
当想用成员里的弱智能指针去访问对象时,使用弱智能指针的lock方法转换返回一个强智能指针去访问对象成员。


正确代码:

#include <iostream>
#include <memory>


class A;
class B;


class A {
public:
    // A 或 B 中至少有一个使用 weak_ptr
    std::weak_ptr<B> pointer;
    ~A() {
        std::cout << "A 被销毁" << std::endl;
    }
};
class B {
public:
    std::shared_ptr<A> pointer;
    ~B() {
        std::cout << "B 被销毁" << std::endl;
    }
};
int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->pointer = b;
    b->pointer = a;


    return 0;
}

智能指针在多线程安全的问题!


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值