std::shared_ptr与std::make_unique在类函数中的使用

在最近学习cartographer算法的时候,发现源码中大量的使用了std::shared_ptr与std::make_unique,对于这些东西之前不是很了解,为了更好的理解源代码,因此简单学习了一下这块内容的使用,在这里简单记个笔记。

std::shared_ptr

shared_ptr是一种智能指针(smart pointer),作用有如同指针,但会记录有多少个shared_ptrs共同指向一个对象。这便是所谓的引用计数(reference counting)。
一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。

其创建方式主要包含以下几种:
1、空shared_ptr

shared_ptr <T>  ptr;

2、使用new创建

shared_ptr<T> ptr(new T());

3、使用复制构造函数,或者重载=构造

shared_ptr<T> ptr1(new T()); 
shared_ptr<T> ptr2(ptr1);

示例:

shared_ptr<int> sp(new int(10));                //一个指向整数的shared_ptr    
assert(sp.unique());                            //现在shared_ptr是指针的唯一持有者     
shared_ptr<int> sp2 = sp;                       //第二个shared_ptr,拷贝构造函数     
assert(sp == sp2 && sp.use_count() == 2);       //两个shared_ptr相等,指向同一个对象,引用计数为2    
*sp2 = 100;                                     //使用解引用操作符修改被指对象    
assert(*sp == 100);                             //另一个shared_ptr也同时被修改     
sp.reset();                                     //停止shared_ptr的使用    
assert(!sp); 

有些东西不是完全理解,先放这儿,后面看得多了写的多了自然就理解了。

此外,与shared_ptr相反的还有unique_ptr,它的区别在于unique_ptr实现独占式拥有(exclusive ownership)或严格拥有(strict ownership)概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(resourece leak)——例如“以new创建对象后因为发生异常而忘记调用delete”——特别有用。

std::make_unique

std::make_unique 是 C++11 标准引入的一个模板函数,用于动态分配指定类型的内存,并返回一个指向分配内存的唯一指针 (即 std::unique_ptr)。

std::make_unique 的语法如下:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args);

其中,T 是指定的类型,Args 是可变长模板参数包,用于传递给指定类型的构造函数的参数。在调用 std::make_unique 时,通过 Args 包传入构造函数的参数会被转发给类型 T 的构造函数,以生成相应的对象实例。该函数返回的指针是一个 std::unique_ptr 类型,表示一个拥有指向动态内存的所有权的对象。

为了方便理解std::shared_ptr与std::make_unique,我们在这里举个例子:

例1

#include "ros/ros.h"  
#include "std_msgs/String.h"
using namespace std;
 
class A {
public:
    //std::shared_ptr<B> pointer;
    ~A() {
        std::cout << "A was destroyed" << std::endl;
    }
    void printdata()
    {
        std::cout << "print A class data" << std::endl;
    }
};

class share_test
{
    public:
    share_test();
    ros::Subscriber data_sub;
    void init();
    private:
    std::shared_ptr<A> a;
    void dataCallback(const std_msgs::String::ConstPtr &msg);
};
share_test::share_test()
{
    ros::NodeHandle n;
	ros::NodeHandle private_nh("~");
    data_sub = n.subscribe<std_msgs::String>("/data_pub",1,&share_test::dataCallback,this);
}


void share_test::init()
{
    a = std::make_unique<A>();
    //std::shared_ptr<A> a = std::make_shared<A>();
}

void share_test::dataCallback(const std_msgs::String::ConstPtr &msg)
{
    a->printdata();
}
int main(int argc, char **argv)  
{
    ros::init(argc, argv, "share_test");  
    share_test share_test;
    share_test.init();
    ros::spin();  
    return 0;
}

在上述函数中,定义了两个类函数A以及share_test,在函数初始化中我们初始化了share_test类,同时在share_test中声明了一个shared_ptr指针:

std::shared_ptr<A> a;

注意这里创建的是一个空指针,上述创建方法中第一种。然后我们在下面的init函数中使用make_unique将其创建为动态分配的智能指针:

a = std::make_unique<A>();

此时,原来的空指针就变成了指向类A的智能指针。这里有个问题,在《C++ 的 make_unique》这篇博客中使用:

auto a = std::make_unique<A>();

的方式建立了指向A的智能指针时,a的指针类型为unique_ptr,但是这里我初始化时将其初始化为shared_ptr似乎也是一样的?不是很理解。

在完成这一步之后,就有了一个指向class A的智能指针。然后我们就可以调用class A中的相关函数,例如这里我们在订阅data_pub的回调函数中引用了A的print函数,编译运行后新开一个终端输入:

rostopic pub -1 /data_pub std_msgs/String "data: 'SSS'"

就可以在节点这边的终端显示如下输出:
在这里插入图片描述
可以看到这里打印出了class A中的打印输出。通过这种指针的方式我们可以很容易的引用一些其他类函数进行需求的解算

例2

再看一个新的例子:

#include "ros/ros.h"  
#include "std_msgs/String.h"
using namespace std;

class C
{
public:
    double input_1;
    string input_2;
    C(double data1,string data2){
        std::cout << "data1 is:" << data1 << std::endl;
        std::cout << "data2 is:" << data2 << std::endl;
        input_1 = data1;
        input_2 = data2;
    }
    void printdata()
    {
        std::cout << "print C class data" << std::endl;
        std::cout << "input_1 is:" << input_1 << std::endl;
        std::cout << "input_2 is:" << input_2 << std::endl;
    }
    ~C() {
        std::cout << "C was destroyed" << std::endl;
    }
};


class share_test
{
    public:
    share_test();
    ros::Subscriber data_sub;
    void init();
    private:
    std::shared_ptr<C> c;
    std::shared_ptr<C> c2;
    void dataCallback(const std_msgs::String::ConstPtr &msg);
};
share_test::share_test()
{
    ros::NodeHandle n;
	ros::NodeHandle private_nh("~");
    data_sub = n.subscribe<std_msgs::String>("/data_pub",1,&share_test::dataCallback,this);
}


void share_test::init()
{
    c = std::make_unique<C>(1,"abc");
    c2 = std::make_unique<C>(2,"abcd");
    //std::shared_ptr<A> a = std::make_shared<A>();
}

void share_test::dataCallback(const std_msgs::String::ConstPtr &msg)
{
    c->printdata();
    c2->printdata();
}
int main(int argc, char **argv)  
{
    ros::init(argc, argv, "share_test");  
    share_test share_test;
    share_test.init();
    ros::spin();  
    return 0;
}

这里与上面的例子略有不同,这里的calss C定义了一个初始化函数,需要传递两个参数,同时在它的print函数中会打印这两个参数。而在class share_test中创建了两个指针:

    std::shared_ptr<C> c;
    std::shared_ptr<C> c2;

随后我们再次使用make_unique创建了两个指向class C的智能指针:

    c = std::make_unique<C>(1,"abc");
    c2 = std::make_unique<C>(2,"abcd");

编译运行这个程序,终端会显示如下:
在这里插入图片描述
可以看到这里输出了4条打印,这是因为share_test::init()函数初始化了两个智能指针,class C这个类作为模板类被使用了两次。

而后,我们再次触发一下subscriber回调函数,可以显示打印如下:
在这里插入图片描述
到这里执行了两次打印调用:

c->printdata();
c2->printdata();

这两个智能指针虽然是指向了同一个模板类,但是由于我们在创建的时候传参是不一样的,因此它们的打印结果也是不一样的,这个就是模板类与智能指针的好处了,通常我们只需要定一一个类函数,然后通过这样一个make_unique的方式创建一个智能指针,就可以使用它里面的一些函数,同时,由于它的指针是独享的,因此我虽然两个指针指向的是同一个类,但是由于类初始化时传递的参数不一致,最后得到的结果也是不一致的,这就极大的便利了一些重复性类函数的使用,例如在cartographer中,使用的子图的概念,就是通过类函数与指针的形式实现的。

参考:
std::shared_ptr 详解
shared_ptr(共享指针)使用总结
C++11新特性之十三:std::make_unique和std::make_shared
C++ 的 make_unique(含 C++ 代码示例)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/475999.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式软件面试-linux-中高级问题

Linux系统启动过程&#xff1a; BIOS自检并加载引导程序。引导程序&#xff08;如GRUB&#xff09;加载Linux内核到内存。内核初始化硬件&#xff0c;加载驱动&#xff0c;建立内存管理。加载init进程&#xff08;PID为1&#xff09;&#xff0c;通常是systemd或SysVinit。init…

安达发|印刷包装APS生产计划排产系统的商业价值

在当今快速消费和竞争激烈的市场环境中&#xff0c;印刷包装行业面临着前所未有的挑战。随着客户需求的多样化、交付期限的缩短以及原材料价格的波动&#xff0c;传统的生产管理方法已无法满足现代印刷包装企业的复杂需求。为了保持竞争力&#xff0c;企业必须采用先进的生产计…

【Java Web基础】一些网页设计基础(五)

文章目录 1. 图片在盒子内部垂直居中2. 底部设计&#xff0c;图片或子元素居中于父盒子&#xff08;水平、垂直&#xff09;&#xff0c;随着屏幕大小放大或缩小3. 使用thymeleaf的th:each会导致样式失效错乱问题 1. 图片在盒子内部垂直居中 用弹性盒子实现垂直居中及用css实现…

【联邦学习贡献评估——联邦学习优化】

1. 模型复用 贡献评估往往需要计算不同参与方组合的数据价值, 然而模型相关的价值度量指标, 比如测试准确率, 需要基于数据重新训练并评测模型, 这导致了高昂的数据价值度量代价. 为了避免重复训练联邦模型的代价, 考虑复用全体参与方组合下训练联邦模型时各参与方的梯度更新,…

tcp seq ack

seq&#xff08;Sequence Number&#xff09;&#xff1a;32bits&#xff0c;表示这个tcp包的序列号。tcp协议拼凑接收到的数据包时&#xff0c;根据seq来确定顺序&#xff0c;并且能够确定是否有数据包丢失。 ack&#xff08;Acknowledgment Number&#xff09;&#xff1a;3…

【热门话题】深入浅出:npm常用命令详解与实践

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 标题&#xff1a;深入浅出&#xff1a;npm常用命令详解与实践引言一、npm基本概…

基于SpringBoot和Vue的大学生租房系统的设计与实现

今天要和大家聊的是一款今天要和大家聊的是一款基于SpringBoot和Vue的大学生租房系统的设计与实现。 &#xff01;&#xff01;&#xff01; 有需要的小伙伴可以通过文章末尾名片咨询我哦&#xff01;&#xff01;&#xff01; &#x1f495;&#x1f495;作者&#xff1a;李同…

力扣爆刷第102天之hot100五连刷96-100

力扣爆刷第102天之hot100五连刷96-100 文章目录 力扣爆刷第102天之hot100五连刷96-100一、136. 只出现一次的数字二、169. 多数元素三、75. 颜色分类四、31. 下一个排列五、287. 寻找重复数 一、136. 只出现一次的数字 题目链接&#xff1a;https://leetcode.cn/problems/sing…

YOLOv3学习

YOLOv3仅使用卷积层&#xff0c;使其成为一个全卷积网络&#xff08;FCN&#xff09;。文章中&#xff0c;作者提出一个新的特征提取网络&#xff0c;Darknet-53。正如其名&#xff0c;它包含53个卷积层&#xff0c;每个后面跟随着batch normalization层和leaky ReLU层。没有池…

基于springboot的医院医护人员排班系统

技术&#xff1a;springbootvuemysql 一、系统背景 本医护人员排班系统管理员&#xff0c;医护。管理员功能有个人中心&#xff0c;医院信息管理&#xff0c;医护信息管理&#xff0c;医护类型管理&#xff0c;排班信息管理&#xff0c;排班类型管理&#xff0c;科室信息管理&…

vivado 增量实施

增量实施 增量实现是指增量编译的实现阶段设计流程&#xff1a; •通过重用参考设计中的先前布局和布线&#xff0c;保持QoR的可预测性。 •加快地点和路线的编制时间或尝试最后一英里的计时关闭。 下图提供了增量实现设计流程图。该图还说明了增量合成流程。有关增量的更多…

一命通关广度优先遍历

前言 在这篇文章之前&#xff0c;已对非线性结构遍历的另一种方法——深度优先遍历进行了讲解&#xff0c;其中很多概念词都是共用的。为了更好的阅读体验&#xff0c;最好先在掌握或起码了解dfs的基础上&#xff0c;再来阅读本文章&#xff0c;否则因为会有很多概念词看不明白…

每日五道java面试题之mybatis篇(六)

目录&#xff1a; 第一题. MyBatis⽀持动态SQL吗&#xff1f;第二题. 说说Mybatis的⼀级、⼆级缓存&#xff1f;第三题. MyBatis的功能架构是什么样的&#xff1f;第四题. 为什么Mapper接⼝不需要实现类&#xff1f;第五题. 说说Mybatis的插件运⾏原理&#xff0c;如何编写⼀个…

GEE入门及进阶教程|使用表达式处理图像

目录 1.EVI的算术计算 2.使用表达式计算 EVI 3.使用表达式计算 BAI 1.EVI的算术计算 增强植被指数 (EVI) 旨在最大限度地减少 NDVI 的饱和度和其他问题&#xff0c;在叶绿素含量高的地区&#xff08;例如雨林&#xff09;&#xff0c;EVI 不会像 NDVI 那样饱和&#xff08;即…

转置卷积(transposed-conv)

一、什么是转置卷积 1、转置卷积的背景 通常&#xff0c;对图像进行多次卷积运算后&#xff0c;特征图的尺寸会不断缩小。而对于某些特定任务 (如图像分割和图像生成等)&#xff0c;需将图像恢复到原尺寸再操作。这个将图像由小分辨率映射到大分辨率的尺寸恢复操作&#xff0c…

spring boot3登录开发-2(2短信验证码接口实现)

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途 目录 写在前面 上文衔接 内容简介 短信验证码接口实现 1.依赖导入 2.接口分析 3.实现思路 3.功能实现 创建发送短信…

USART串口控制LED灯

#include "uart4.h"void uart4_init() {//使能GPIORCC->MP_AHB4ENSETR |(0x1<<1);RCC->MP_AHB4ENSETR |(0x1<<6);//使能UART4RCC->MP_APB1ENSETR |(0x1<<16) ;//GPIO MODER设置 复用模式设置GPIOB->MODER |(0x2<<4);G…

C语言例:设 int i=2;float f=14;,则执行语句f=(float)i; 后。变量i 的值

代码如下&#xff1a; #include<stdio.h> int main(void) {int i2;float f14;f(float)i;printf("i %d\n",i); //i2//强制转换不会改变数据类型printf("i %.1f\n",i);return 0; } 结果如下&#xff1a;

【Leetcode每日一题】 递归 - 两两交换链表中的节点(难度⭐)(38)

1. 题目解析 题目链接&#xff1a;24. 两两交换链表中的节点 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 一、理解递归函数的含义 首先&#xff0c;我们需要明确递归函数的任务&#xff1a;给定一个链表&#xf…

网速监控,实时网络速度监控

带宽与网速 现在&#xff0c;对高带宽的需求空前高涨&#xff0c;而且网络&#xff08;包括标准的内部部署&#xff09;以及公共、私有和混合环境都变得更加复杂。 虽然带宽和网速经常互换使用&#xff0c;但它们并不总是相同的。网速更多的是与延迟有关&#xff0c;而不是与…