c++智能指针的使用

官方参考

普通指针的烦恼:内存泄漏,多次释放,提前释放

智能指针 负责自动释放所指向的对象。

三种智能指针 shared_ptr,unique_ptr,weak_ptr;

将shared_ptr存放在一个容器中,不再需要它的时候,要erase掉。

allocator负责封装堆内存管理的对象,它们在整个标准库中使用,特别是STL容器使用它们来管理容器内部的所有内存分配,大部份情况下,程序员不用理会,标准容器使用默认的分配器称为std :: allocator。

shared_ptr

shared_ptr

多个指针指向相同的对象;

使用引用计数,引用计数是线程安全的,但是对象的读写需要加锁。

不可以直接将指针直接赋值给一个智能指针,因为指针指针是一个类。

get获取原始指针

最大的陷阱就是循环引用,这会导致内存无法正确释放,导致内存泄漏

#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex> struct Base
{
Base() { std::cout << " Base::Base()\n"; }
// 注意:此处非虚析构函数 OK
~Base() { std::cout << " Base::~Base()\n"; }
}; struct Derived: public Base
{
Derived() { std::cout << " Derived::Derived()\n"; }
~Derived() { std::cout << " Derived::~Derived()\n"; }
}; void thr(std::shared_ptr<Base> p)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::shared_ptr<Base> lp = p; // 线程安全,虽然自增共享的 use_count
{
static std::mutex io_mutex;
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << "local pointer in a thread:\n"
<< " lp.get() = " << lp.get()
<< ", lp.use_count() = " << lp.use_count() << '\n';
}
} int main()
{
std::shared_ptr<Base> p = std::make_shared<Derived>(); std::cout << "Created a shared Derived (as a pointer to Base)\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';
std::thread t1(thr, p), t2(thr, p), t3(thr, p);
p.reset(); // 从 main 释放所有权
std::cout << "Shared ownership between 3 threads and released\n"
<< "ownership from main:\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';
t1.join(); t2.join(); t3.join();
std::cout << "All threads completed, the last one deleted Derived\n";
}

可能的输出:

Base::Base()
Derived::Derived()
Created a shared Derived (as a pointer to Base)
p.get() = 0x2299b30, p.use_count() = 1
Shared ownership between 3 threads and released
ownership from main:
p.get() = 0, p.use_count() = 0
local pointer in a thread:
lp.get() = 0x2299b30, lp.use_count() = 5
local pointer in a thread:
lp.get() = 0x2299b30, lp.use_count() = 3
local pointer in a thread:
lp.get() = 0x2299b30, lp.use_count() = 2
Derived::~Derived()
Base::~Base()
All threads completed, the last one deleted Derived

weak_ptr

是为了配合shared_ptr而引入的一种智能指针,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。

weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。

成员函数expired()的功能等价于use_count()==0,

weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象

#include <iostream>
#include <memory> std::weak_ptr<int> gw; void observe()
{
std::cout << "use_count == " << gw.use_count() << ": ";
if (auto spt = gw.lock()) { // 使用之前必须复制到 shared_ptr
std::cout << *spt << "\n";
}
else {
std::cout << "gw is expired\n";
}
} int main()
{
{
auto sp = std::make_shared<int>(42);
gw = sp; observe();
} observe();
}

输出:

use_count == 1: 42
use_count == 0: gw is expired

unique_ptr

unique_ptr

唯一拥有对象

通过reset方法重新指定

通过release方法释放所有权

#include <iostream>
#include <vector>
#include <memory>
#include <cstdio>
#include <fstream>
#include <cassert>
#include <functional> struct B {
virtual void bar() { std::cout << "B::bar\n"; }
virtual ~B() = default;//父类的析构函数需要定义为虚函数,防止内存泄漏
};
struct D : B
{
D() { std::cout << "D::D\n"; }
~D() { std::cout << "D::~D\n"; }
void bar() override { std::cout << "D::bar\n"; }
}; // 消费 unique_ptr 的函数能以值或以右值引用接收它
std::unique_ptr<D> pass_through(std::unique_ptr<D> p)
{
p->bar();
return p;
} void close_file(std::FILE* fp) { std::fclose(fp); } int main()
{
std::cout << "unique ownership semantics demo\n";
{
auto p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr
auto q = pass_through(std::move(p));
assert(!p); // 现在 p 不占有任何内容并保有空指针
q->bar(); // 而 q 占有 D 对象
} // ~D 调用于此 std::cout << "Runtime polymorphism demo\n";
{
std::unique_ptr<B> p = std::make_unique<D>(); // p 是占有 D 的 unique_ptr
// 作为指向基类的指针
p->bar(); // 虚派发 std::vector<std::unique_ptr<B>> v; // unique_ptr 能存储于容器
v.push_back(std::make_unique<D>());
v.push_back(std::move(p));
v.emplace_back(new D);
for(auto& p: v) p->bar(); // 虚派发
} // ~D called 3 times std::cout << "Custom deleter demo\n";
std::ofstream("demo.txt") << 'x'; // 准备要读的文件
{
std::unique_ptr<std::FILE, void (*)(std::FILE*) > fp(std::fopen("demo.txt", "r"),
close_file);
if(fp) // fopen 可以打开失败;该情况下 fp 保有空指针
std::cout << (char)std::fgetc(fp.get()) << '\n';
} // fclose() 调用于此,但仅若 FILE* 不是空指针
// (即 fopen 成功) std::cout << "Custom lambda-expression deleter demo\n";
{
std::unique_ptr<D, std::function<void(D*)>> p(new D, [](D* ptr)
{
std::cout << "destroying from a custom deleter...\n";
delete ptr;
}); // p 占有 D
p->bar();
} // 调用上述 lambda 并销毁 D std::cout << "Array form of unique_ptr demo\n";
{
std::unique_ptr<D[]> p{new D[3]};
} // 调用 ~D 3 次
}

输出:

unique ownership semantics demo
D::D
D::bar
D::bar
D::~D
Runtime polymorphism demo
D::D
D::bar
D::D
D::D
D::bar
D::bar
D::bar
D::~D
D::~D
D::~D
Custom deleter demo
x
Custom lambda-expression deleter demo
D::D
D::bar
destroying from a custom deleter...
D::~D
Array form of unique_ptr demo
D::D
D::D
D::D
D::~D
D::~D
D::~D

shared_ptr循环引用的内存泄漏问题

如下对象建模——家长与子女:a Parent has a Child, a Child knowshis/her Parent。

从程序的运行中可以看到最终资源没有得到释放。

一个智能指针在创建一个对象的时候初始化引用计数为 1,并把自己的指针指向创建的对象。但这个引用计数在何处?在智能指针内部?非也,这个计数是一个单独的对象来实现的,如图1,当另外一个智能指针指向这个对象的时候,便找到与这个对象对应的计数对象,并加一个引用,即 use_count++。这样多个智能指针对象便可以使用相同的引用计数。

下面程序中,当指针p释放时,由于指针c->ParentPtr还在引用着new Child,所以这时(new Child)的use_count从2减为1。同理当指针c释放时,由于p->ChildPtr还在引用着new Parent,所以这时(new Parent)的use_count从2减为1。最终,内存没有被释放完全。

class Child;
class Parent; class Parent {
private:
std::shared_ptr<Child> ChildPtr;
public:
void setChild(std::shared_ptr<Child> child) {
this->ChildPtr = child;
} void doSomething() {
if (this->ChildPtr.use_count()) {
}
} ~Parent() {}
}; class Child {
private:
std::shared_ptr<Parent> ParentPtr;
public:
void setPartent(std::shared_ptr<Parent> parent) {
this->ParentPtr = parent;
}
void doSomething() {
if (this->ParentPtr.use_count()) {
}
}
~Child() {}
}; int main() {
std::weak_ptr<Parent> wpp;
std::weak_ptr<Child> wpc;
{
std::shared_ptr<Parent> p(new Parent);
std::shared_ptr<Child> c(new Child);
std::cout << "p.use_count() = " << p.use_count() << std::endl;
std::cout << "c.use_count() = " << c.use_count() << std::endl;
p->setChild(c);
c->setPartent(p);
std::cout << "p.use_count() = " << p.use_count() << std::endl;
std::cout << "c.use_count() = " << c.use_count() << std::endl;
wpp = p;
wpc = c; std::cout << "p.use_count() = " << p.use_count() << std::endl; // 2
std::cout << "c.use_count() = " << c.use_count() << std::endl; // 2
cout<<endl; }
std::cout << "p.use_count() = " << wpp.use_count() << std::endl; // 1
std::cout << "c.use_count() = " << wpc.use_count() << std::endl; // 1
return 0;
}

运行结果

p.use_count() = 1
c.use_count() = 1
p.use_count() = 2
c.use_count() = 2
p.use_count() = 2
c.use_count() = 2 p.use_count() = 1
c.use_count() = 1

shared_ptr循环引用的内存泄漏问题解决

如下,在两个需要互相引用的类的内部,使用weak_ptr智能指针引用对方,来避免循环引用导致的内存泄漏问题。

#include <iostream>
#include <memory> class Child;
class Parent; class Parent {
private:
//std::shared_ptr<Child> ChildPtr;
std::weak_ptr<Child> ChildPtr;
public:
void setChild(std::shared_ptr<Child> child) {
this->ChildPtr = child;
} void doSomething() {
//new shared_ptr
if (this->ChildPtr.lock()) { }
} ~Parent() {
}
}; class Child {
private:
std::shared_ptr<Parent> ParentPtr;
public:
void setPartent(std::shared_ptr<Parent> parent) {
this->ParentPtr = parent;
}
void doSomething() {
if (this->ParentPtr.use_count()) { }
}
~Child() {
}
}; int main() {
std::weak_ptr<Parent> wpp;
std::weak_ptr<Child> wpc;
{
std::shared_ptr<Parent> p(new Parent);
std::shared_ptr<Child> c(new Child);
p->setChild(c);
c->setPartent(p);
wpp = p;
wpc = c;
std::cout << p.use_count() << std::endl; // 2
std::cout << c.use_count() << std::endl; // 1
}
std::cout << wpp.use_count() << std::endl; // 0
std::cout << wpc.use_count() << std::endl; // 0
return 0;
}

运行结果

2100

更多编程资料详见公众号 xutopia77

c++智能指针的使用,shared_ptr,unique_ptr,weak_ptr的更多相关文章

  1. C++智能指针 auto_ptr、shared_ptr、weak_ptr和unique_ptr

    手写代码是理解C++的最好办法,以几个例子说明C++四个智能指针的用法,转载请注明出处. 一.auto_ptr auto_ptr这是C++98标准下的智能指针,现在常常已经被C++标准的其他智能指针取 ...

  2. 聊聊智能指针 auto_ptr、shared_ptr、weak_ptr和unique_ptr

    本文为转载:https://www.cnblogs.com/zeppelin5/p/10083597.html,对作者有些地方做了修正. 手写代码是理解C++的最好办法,以几个例子说明C++四个智能指 ...

  3. c++——智能指针学习(shared_ptr和weak_ptr)

    先看一个例子:Stark和Targaryen家族你中有我,我中有你.我们设计以下类企图避免内存泄漏,使得析构函数都能调用到: #include<iostream> #include< ...

  4. C++11 新特性之智能指针(shared_ptr, unique_ptr, weak_ptr)

    这是C++11新特性介绍的第五部分,涉及到智能指针的相关内容(shared_ptr, unique_ptr, weak_ptr). shared_ptr shared_ptr 基本用法 shared_ ...

  5. 智能指针思想实践(std::unique_ptr, std::shared_ptr)

    1 smart pointer 思想 ​ 个人认为smart pointer实际上就是一个对原始指针类型的一个封装类,并对外提供了-> 和 * 两种操作,使得其能够表现出原始指针的操作行为. ​ ...

  6. shared_ptr & unique_ptr & weak_ptr (C++11)

    c++11标准废除乐auto_ptr, C++ 标准库智能指针 使用这些智能指针作为将指针封装为纯旧 C++ 对象 (POCO) 的首选项. unique_ptr 只允许基础指针的一个所有者. 除非你 ...

  7. C++智能指针: auto_ptr, shared_ptr, unique_ptr, weak_ptr

    本文参考C++智能指针简单剖析 内存泄露 我们知道一个对象(变量)的生命周期结束的时候, 会自动释放掉其占用的内存(例如局部变量在包含它的第一个括号结束的时候自动释放掉内存) int main () ...

  8. C++ 智能指针 auto_ptr 和 shared_ptr

    首先,如果你不知道什么是智能指针,请先移步:C++智能指针简单剖析 1.auto_ptr #ifndef AUTO_PTR_H #define AUTO_PTR_H template<typen ...

  9. 【C++】智能指针简述(四):shared_ptr

    在开始本文内容之前,我们再来总结一下,前文内容: 1.智能指针采用RAII机制,在构造对象时进行资源的初始化,析构对象时进行资源的清理及汕尾. 2.auto_ptr防止拷贝后析构释放同一块内存,采用& ...

  10. 智能指针-共享式shared_ptr

    #include <iostream>#include <string>#include <vector>#include <memory> using ...

随机推荐

  1. postgresql很强大,为何在中国,mysql成为主流?

    你找一个能安装起来的数据库,都可以学,不管什么版本. 数据库的基本功,是那些基本概念(SQL,表,存储过程,索引,锁,连接配置等等),这些在任何一个版本中都是一样的. 目录 postgresql很强大 ...

  2. UNCTF2020 pwn题目

    YLBNB 用pwntools直接连接,然后接受就行. 1 from pwn import * 2 3 p = remote('45.158.33.12',8000) 4 context.log_le ...

  3. java 数据类型:<泛型>在方法中和在构造器中的应用

    背景: Java不允许我们把对象放在一个未知的集合中. import java.util.ArrayList; import java.util.List; /** * @ClassName Meth ...

  4. python3 5月26日 time模块常用时间转换 &datetime()模块学习 random()

    import time  获取当前时间: 指定字符串格式:time.strftime("%Y-%m-%d %H:%M:%S") 当前时间戳:time.time() 当前时间元组格式 ...

  5. host-manager does not exist or is not a readable directory

    当tomcat启动出现这个错误时,按照如下步骤可以解决: 1.删掉F:\tomcat20111101\apache-tomcat-6.0.26\conf\Catalina目录下的localhost文件 ...

  6. vue+el-element中根据文件名动态创建dialog的方法

    背景 在项目中使用对话框的通常做法是把对话框封装成组件,在使用的地方引入,然后添加到template,使用visible.sync控制对话框的显示/隐藏,监听confirm事件处理用户点击确定.如下: ...

  7. git pull 拉取报错:fatal: refusing to merge unrelated histories

    fatal: refusing to merge unrelated histories(拒绝合并不相关的历史) 使用 git pull origin master --allow-unrelated ...

  8. SpringBoot整合quartz框架启动定时任务报错:the given trigger will never fire.

    org.quartz.SchedulerException: Based on configured schedule, the given trigger 'DEFAULT.cron_b1a91e1 ...

  9. 一个在线MP4提取mp3的网站

    网址 https://airmore.cn/extract-audio-online

  10. 细聊.NET6 ConfigurationManager的实现

    前言 友情提示:建议阅读本文之前先了解下.Net Core配置体系相关,也可以参考本人之前的文章<.Net Core Configuration源码探究>然后对.Net Core的Conf ...