Effective Modern C++ 条款21 比起直接使用new,更偏爱使用std::make_unique和std::make_shared

2023-05-16

比起直接使用new,更偏爱使用std::make_unique和std::make_shared

让我们从std::make_uniquestd::make_shared之间的比较开始讲起吧。std::make_shared是C++11的一部分,可惜的是,std::make_unique不是,它在C++14才纳入标准库。如果你使用的是C++11,不用忧伤,因为std::make_unique的简单版本很容易写出来,不信你看:

template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&&... params)
{
    return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}

就像你看到的那样,make_unique只是把参数完美转发给要创建对象的构造函数,再从new出来的原生指针构造std::unique_ptr,最后返回创建的std::unique_ptr。这种形式的函数不支持数组和自定义删除器(看条款18),但它说明了只要一点点工作,你就可以创造你需要的make_unique了。你要记住不要把你自己的版本放入命名空间std,因为当你提升到C++14标准库实现的时候,你不会想要它和标准库的版本冲突。

std::make_uniquestd::make_shared是三个make函数中的其中两个,而make函数是:把任意集合的参数完美转发给动态分配对象的构造函数,然后返回一个指向那对象的智能指针。第三个make函数是std::allocate_shared,它的行为与std::make_shared类似,除了它第一个参数是个分配器,指定动态分配对象的方式。

通过琐碎比较使用make函数和不使用make函数创建智能指针,揭露了使用make函数更可取的第一个原因。考虑以下:

auto upw1(std::make_unique<Widget>());  // 使用make函数

std::unique_ptr<Widget> upw2(new Widget);  // 不使用make函数

auto spw1(std::make_shared<Widget>());  // 使用make函数

std::shared_ptr<Widget> spw2(new Widget);  // 不使用make函数

它们本质上的不同是:使用new的版本重复着需要创建的类型(即出现了两次Widget),而使用make’函数不需要。重复出现类型和软件工程的关键原则产生冲突:应该避免代码重复。源码中重复的代码会增加编译时间,导致对象代码膨胀,并且通常会让代码库更难运行——这经常引发不合逻辑的代码,而不合逻辑的代码库一般会出现bug。除非是写两次比写一个更有效果,不然谁不喜欢少些点代码吗?


更偏爱使用make函数的第二个原因异常安全。假如我们有个函数,根据优先级来处理Widget:

void processWidget(std::shared_ptr<Widget> spw, int priority);

值传递std::shared看起来有点奇怪,不过条款41会解释如果processWidget内部总是复制std::shared_ptr(例如,把它存储在一个数据结构中,这个数据结构会监测Widget是否被处理),这设计是个合理的选择。

现在呢,假如我们有个计算优先级的函数,

int computePriority();

然后我们用它和new创建的智能指针作为参数调用processWidget:

processWidget(std::shared_ptr<Widget>(new Widget), 
              computePriority());    // 可能会资源泄漏

就如注释所说,这代码中new出来的Widget可能会泄漏,但是为什么?std::shared_ptr是为了防止资源泄漏而设计的,当最后一个指向资源的std::shared_ptr对象消失,它们指向的资源也会被销毁。如果每个人无论什么地方都使用std::shared_ptr,C++还有内存泄漏这回事吗?

答案是在编译期间,源代码转换为目标码时(*.o文件)。在运行时间,函数的参数在函数运行前必须被求值,所以调用processWidget时,下面的事请会在processWidget开始前执行:

  • 表达式“new Widget”会被求值,即,一个Widget对象必须在堆上被创建。
  • std::shared_ptr的接收原生指针的构造函数一定要执行。
  • computePriority一定要运行。

编译器在生成代码时不会保证上面的执行顺序,“new Widget”一定会在std::shared_ptr构造函数之前执行,因为构造函数需要new的结果,但是computePriority可能在它们之前就被调用了,可能在它们之后,可能在它们之间。所以,编译器生成代码的执行顺序有可能是这样的:

  1. 执行“new Widget”。
  2. 执行computePriority。
  3. 执行std::shared_ptr的构造函数。

如果生成的代码真的是这样,那么在运行时,computePriority产生了异常,步骤1中动态分配的Widget就泄漏了,因为它没有被步骤3中的std::shared_ptr保存。

使用std::make_shared_ptr可以避免这问题。这样调用代码:

processWidget(std::make_shared<Widget>(), computePriority())

在运行期间,std::make_sharedcomputePriority都有可能先被调用,如果先调用的是std::make_shared,那么指向动态分配Widget对象的原生指针会安全地存储在要返回的std::shared_ptr中,然后再调用computePriority。如果computePriority产出异常,std::shared_ptr的析构函数就会销毁持有的Widget。而如果先调用的是computePriority,并且产生异常,std::make_shared就不会被执行,因此没有动态分配的Widget对象让你担心。

如果我们把std::shared_ptrstd::make_shared替换成std::unique_ptrstd::make_unique,效果一样。使用std::make_unique替代new的重要性就像使用std::make_shared那样:写异常安全的代码。


std::make_shared的一个特点(相比于直接使用new)是提高效率。使用std::make_shared允许编译器生成更小、更快的代码。考虑当我们直接使用new时:

std::shared_ptr<Widget> spw(new Widget);

很明显这代码涉及一次内存分配,不过,它实际上分配两次。条款19说明每个std::shared_ptr内都含有一个指向控制块的指针,这控制块的内存是由std::shared_ptr的构造函数分配的,那么直接使用new,需要为Widget分配一次内存,还需要为控制块分配一次内存。

如果用std::make_shared呢,

auto spw = std::make_shared<Widget>();

一次分配就够了,因为std::make_shared会分配一大块内存来同时持有Widget对象和控制块。这种优化减少了程序的静态尺寸,因为代码只需要调用一次内存分配函数,然后它增加了代码执行的速度,因为只需要分配一次内存(说明是分配内存这个函数开销略大)。而且,使用std::make_shared能避免了一些控制块的簿记信息,潜在地减少了程序占用的内存空间。

std::allocate_shared的性能分析和std::make_shared一样,所以std::make_shared的性能优势也可以延伸到std::allocate_shared


比起直接使用new,更偏爱使用make函数,这个争论是很热烈的。虽有软件工程、异常安全、性能优势,不过,本条款的指导方针是更偏爱使用make函数,而不是单独依赖它们,这是因为在某些状况下它们不适用。

例如,没有一个make函数可以指定自定义删除器(看条款18和19),但是std::unique_ptrstd::shared_ptr都有这样的构造函数。给定一个Widget的自定义删除器,

auto widgetDeleter = [](Widget* pw) {...}

我们可以直接使用new创建智能指针:

std::unique_ptr<Widget, decltype(widgetDeleter)>
    upw(new Widget, widgetDeleter);

std::shared_ptr<Widget> spw(new Widget, widgetDeleter);

make函数就做不来这种事情。


make函数的第二个限制是来源于它们实现的句法细节。条款7说明当创建一个对象时,如果该对象的重载构造函数带有std::initializer_list参数,那么使用大括号创建对象会偏向于使用带std::initializer_list构造,要使用圆括号创建对象才能使用到非std::initializer_list构造。make函数把它们的参数完美转发给对象的构造函数,那么它们用的是大括号还是圆括号呢?对于某些类型,这问题的答案的不同会导致结果有很大差异。例如,在这些调用中,

auto upv = std::make_unique<std::vector<int>>(10, 20);

auto spv = std::make_shared<std::vector<int>>(10, 20);

指针指向的是带10个元素、每个值为20的std::vector呢,还是指向两个元素、一个10、一个20的std::vector呢?还是说结果不能确定吗?

好消息是结果是能确定的:上面两个都创建内含10个值为20的std::vector。那意味着在make函数内,完美转发使用的是圆括号,而不是大括号。坏消息是如果你想用大括号初始化来构造指向的对象,你只能直接使用new,如果你想使用make函数,就要求完美转发的能力支持大括号初始化,但是条款30说明,大括号初始化不能被完美转发。不过条款30也讲了一种能工作的方法:用auto推断大括号,从而创建一个std::initializer_list对象(条款2),然后把auto变量传递给make函数:

// 创建 std::initializer_list
auto initList = {10, 20};

// 使用std::initializer_list构造函数创建std::vector,容器中只有两个元素
auto spv = std::make_shared<std::vector<int>>(initList);

对于std::unique_ptr,只有两种情况(自定义删除器和大括号初始化)会让它的make函数出问题。对于std::shared_ptr和它的make函数,就多两种情况,这两种情况都是边缘情况,不过一些开发者就喜欢住在边缘,你可能就是他们中第一个。

一些类定义了自己的operator newoperator delete函数,这些函数的出现暗示着常规的全局内存分配和回收不适合这种类型的对象。通常情况下,设计这些函数只有为了精确分配和销毁对象,例如,Widget对象的operator newoperator delete只有为了精确分配和回收大小为sizeof(Widget)的内存块才会设计。这两个函数不适合std::shared_ptr的自定义分配(借助std::allocate_shared)和回收(借助自定义删除器),因为std::allocate_shared请求内存的大小不是对象的尺寸,而是对象尺寸加上控制块尺寸。结果就是,使用make函数为那些——定义自己版本的operator newoperator delete的——类创建对象是个糟糕的想法。


比起直接使用newstd::make_shared的占用内存大小和速度优势来源于:std::shared_ptr的控制块与它管理的对象放在同一块内存。当引用计数为0时,对象被销毁(即调用了析构函数),但是,它使用的内存不会释放,除非控制块也被销毁,因为对象和控制块在同一块动态分配的内存上。

就像我提起那样,控制块上除了引用计数还有别的薄记信息。引用计数记录的是有多少std::shared_ptr指向控制块,但是控制块还有第二种引用计数,记录有多少std::weak_ptr指向控制块。这种引用计数称为weak count。当std::weak_ptr检查它是否过期时(expired,看条款20),它通过检查控制块中的引用计数(不是weak count)来实现。如果引用计数为0(即没有std::shared_ptr指向这个对象,因此被销毁),std::weak_ptr就过期,否则就没有过期。

但是,只要有std::weak_ptr指向控制块(weak count大于0),控制块就必须继续存在,而只要控制块存在,容纳它的内存块也依旧存在。那么,通过make函数创建对象分配的内存,要直到最后一个指向它的std::shared_ptrstd::weak_ptr对象销毁,才能被回收。

如果对象的类型非常大,并且最后一个std::shared_ptr销毁和最后一个std::weak_ptr销毁之间的时间间隔很大,那么是对象销毁和内存被回收之间的会有延迟:

class ReallyBigType { ... };

auto pBigObj =                          // 借助std::make_shared
   std::make_shared<ReallyBigType>();   // 创建类型非常大的对象
...               // 创建std::shared_ptr和std::weak_ptr指向对象 
...               // 最后一个std::shared_ptr被销毁,那仍有std::weak_ptr存在
...               // 在这个期间,之前类型非常大的对象使用的内存仍然被占用
...               // 最后一个std::weak被销毁,控制块和对象共占的内存被释放

如果直接使用new,ReallyBigType对象的内存只要在最后一个std::shared_ptr被销毁就能被释放:

class ReallyBigType { ... };        // 如前

std::shared_ptr<ReallyBigType> pBigObj(new ReallyBigType); //借助new创建
...              // 如前,创建std::shared_ptr和std::weak_ptr指向对象
...              // 最后一个std::shared_ptr被销毁,仍有std::weak_ptr存在
                 // 对象的内存被回收
...              // 在这期间,只有控制块的内存被占用
...              // 最后一个指向对象的std::weak_ptr被销毁,控制块的内存被释放

当你发现某些情况不能使用或者不适合使用std::make_shared,却又想要防止容易发生的异常安全问题。最好的办法就是确保当你直接使用new时,用一条语句执行——把new的结果马上传递给智能指针的构造函数,并且该语句就做这一件事。这防止编译器生成newstd::shared_ptr构造之间发出异常。

作为例子,我们修改之前的异常不安全processWidget,并指定自定义删除器:

void processWidget(std::shared_ptr<Widget> spw, int priority); // 如前

void cusDel(Widget *ptr);      //  自定义删除器

这里是异常不安全的调用:

processWidget(           // 如前,可能资源泄漏
   std::shared_ptr<Widget>(new Widget, cusDel),
   computePriority()
);

回忆:如果computePriority调用在“new Widget”之前,std::shared_ptr构造之后,然后computePriority产生异常,那么动态分配的Widget就会泄漏。

这里要使用自定义删除器,不能使用std::make_shared,所以避免泄漏的方法就是把分配Widget和std::shared_ptr构造放在只属于它们的语句,然后再用std::shared_ptr的结果调用processWidget。这是这项技术的本质部分,等下我们可见到,我们可以修改它从而提高性能:

std::shared_ptr<Widget> spw(new Widget, cusDel);

processWidget(spw, computeWidget);  // 正确,但没有优化,看下面

这代码是可行的,因为std::shared_ptr得到了原生指针的所有权,尽管构造函数可能发出异常。在这个例子中,如果spw的构造期间抛出异常(例如,由于不能为控制块动态分配内存),也能保证cusDel被调用(以“new Widget”的结果为参数)。

有个小小的性能问题,在异常不安全的调用中,我们传给processWidget的是一个右值,

processWidget(
   std::shared_ptr<Widget>(new Widget, cusDel),  // 参数是右值
   computePriority()
);

但是在异常安全的调用中,我们传递的是个左值:

processWidget(spw, computePriority());   // 参数是左值

因为processWidget的std::shared_ptr参数是值传递,从一个右值构造使用的是移动,从一个左值构造使用的是拷贝。对于std::shared_ptr,这差别挺大的,因为拷贝一个std::shared_ptr需要增加它的引用计数,这是原子操作,而移动操作完全不用操作引用计数。针对于异常安全代码想要达到异常不安全代码的性能水平,我们需要使用std::move来把spw转化为右值(看条款23):

processWidget(std::move(spw), computePriority());  // 现在也一样高效

这是有趣的而且值得知道,但是通常也是不相干的,因为你很少有理由不用make函数,除非你有迫不得已的理由,否则,你应该使用make函数。

总结

需要记住的3点:

  • 相比于直接使用new,make函数可以消除代码重复,提高异常安全,而且std::make_sharedstd::allocate_shared生成的代码更小更快。
  • 不适合使用make函数的场合包括需要指定自定义删除器和想要传递大括号初始值。
  • 对于std::shared_ptr,使用make函数可能是不明智的额外场合包括(1)自定义内存管理函数的类和(2)内存紧张的系统中,有非常大的对象,然后std::weak_ptrstd::shared_ptr长寿。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Effective Modern C++ 条款21 比起直接使用new,更偏爱使用std::make_unique和std::make_shared 的相关文章

  • C++ 装饰 basic_iostream 类

    我想做一些像下面的代码所示的事情 class foo private std fstream m stream public foo std fstream stream m stream stream foo write char con
  • 如何发现 std::vector 的大小/长度(以字节为单位)?

    我有一个向量 我想将其写入文件并将其读取到文件中 但无法使用以下方法确定向量的逻辑大小sizeof操作员 那我该怎么办呢 C std vector 有一个方法size 返回其大小 编辑 据我所知 您现在需要计算给定向量使用的内存 您不能为此
  • std::any 跨越 mingw 中的共享库边界

    我在跨共享库边界使用 mingw 的 libstdc 的 std any 实现时偶然发现了一个问题 它产生一个std bad any cast显然不应该 我相信 我使用 mingw w64 gcc 7 并使用 std c 1z 编译代码 简
  • 使用前瞻的 C++ std::regex 失败

    我需要从磁盘解析 txt 文件 所以我首先做了一个例子来测试正则表达式 这是我的示例代码 std string txt paragraph r nthis is the text file r ni need only this data
  • MATLAB 中的统计异常值检测

    假设我们有这个矩阵 main 10000 5 3 1 5 5677 0 134 1 1 456 3 该方法是计量经济学和统计问题中使用最广泛的方法 X是我们正在寻找异常值的数据 X mean X gt n std X 因此 如果这个不等式成
  • 无法调用 std::max 因为 minwindef.h 定义了“max”[重复]

    这个问题在这里已经有答案了 我将如何实际调用 std max 该代码无法在 Visual Studio 2013 中编译 因为它采用 max 作为宏 std max 在 std 之后需要一个标识符 您可以取消宏定义 undef max Ed
  • 无法在 Mac 上访问 std 向量迭代器 _Ptr

    在 Visual Studio 上 这段代码就像一个魅力 auto child node childs begin while child node childs end vector
  • cc1plus:错误:g++ 无法识别命令行选项“-std=c++11”

    我正在尝试使用编译g 以及 std c 11 or c 0x flags 但是 我收到这个错误 cc1plus error unrecognized command line option std c 11 g 版本 g GCC 4 1 2
  • 特定于平台的 std::chrono::high_resolution_clock::period::num

    我注意到了std chrono high resolution clock period num 1对于我测试过的每个系统 是否存在任何系统 嵌入式 桌面 移动或其他 它恰好是其他数字 在这样的系统上 1 秒不能用刻度来表示 有以下三种实现
  • 我们可以依靠减少容量的技巧吗?

    它真的能保证以下减少容量的技巧在任何地方都能 起作用 吗 int main std string s lololololol s capacity still non zero string s swap s 它似乎对我来说 不起作用 因为
  • 容器模板参数的Value_type

    在他今年的 Going Native 主题演讲中C 的本质 http channel9 msdn com Events GoingNative 2013 Opening Keynote Bjarne Stroustrup 转至 40 30
  • 为什么 std::type_info 不可复制?我可以把它存放在某个地方吗?

    The std type info类是不可复制的 这使得很难将其存储在对象中以供以后使用 我应该怎么办 C 11 中有一个更好的解决方案 一个名为 std type index 的新可复制包装器 您需要包含标头 typeindex 才能使用
  • c++ max_element 每n个元素

    有没有办法比较每 N 个元素来找到容器中的最大元素并返回索引 使用 STL BOOST 或 其他库 对于每个 N 我的意思是使用 std max element 但将 for 的增加从 first 更改为 first n based on
  • 如何在node.js中处理stdout

    我试图自动化每次在服务器上测试应用程序和网站时所经历的过程 我目前正在nodejitsu上运行 当我测试了某些东西并且它可以在我的本地计算机上运行时 我要做的下一件事是 打开我的 package json 文件 删除域字段并将名称和子域更改
  • 从 boost::shared_ptr 转换为 std::shared_ptr?

    我有一个内部使用 Boost 版本的库shared ptr并且只暴露那些 对于我的应用程序 我想使用std shared ptr只要有可能 遗憾的是 这两种类型之间没有直接转换 因为引用计数的内容取决于实现 有什么办法可以同时拥有boost
  • 为什么我不能在 C++ 中的 `std::map` 中存储引用?

    我知道引用不是指针 而是对象的别名 但是 我仍然不明白这对我作为程序员到底意味着什么 即幕后的引用是什么 我认为理解这一点的最好方法是理解为什么我无法在地图中存储参考 我知道我需要停止将引用视为指针的语法糖 只是不知道如何 按照我的理解 引
  • 如何只匹配那些前面有偶数个“%”的数字?

    我想捕获字符串中任何位置出现的数字 并将其替换为 但我只想捕获那些具有偶数个的数字 在他们前面 不用担心周围的字符是否被捕获 我们可以使用捕获组来过滤掉数字 我无法想出 ECMAscript 正则表达式 这是操场 abcd 1 2 3 4
  • 使用accumulate计算数组double[]平均值的函数

    它一定是最常见的函数 每个人在某处都有代码片段 但我实际上花了不少于 1 5 小时在 SO 以及其他 C 网站上搜索它 但还没有找到解决方案 我想计算 a 的平均值double array 使用函数 我想将数组作为函数传递给参考 有数百万个
  • 错误 C2039:“find”:不是“std”的成员

    我刚刚遇到一个奇怪的错误 它说 find 不是 std 的成员 错误 C2039 find 不是 std 的成员 错误 C3861 查找 未找到标识符 基本上 我想查找是否可以在向量中找到字符串 知道为什么会发生这种情况吗 代码帮助告诉我
  • 如何使用 CUDA/Thrust 对两个数组/向量根据其中一个数组中的值进行排序

    这是一个关于编程的概念问题 总而言之 我有两个数组 向量 我需要对一个数组 向量进行排序 并将更改传播到另一个数组 向量中 这样 如果我对 arrayOne 进行排序 则对于排序中的每个交换 arrayTwo 也会发生同样的情况 现在 我知

随机推荐

  • 200个DIY及科技网站

    http www powerlabs org 高能diy 电磁枪 微波武器 http www altair org tesla html 高电压diy爱好者 http www richieburnett co uk tesla shtml
  • Linuxunexpected error reading Dockerfile: is a directory--2021-08-25

    编写完Dockerfile后需要通过命令将其制作为镜像 mobaXterm报错 unexpected error reading Dockerfile read var lib docker tmp docker builder314741
  • 在Eclipse中配置Tomcat

    简介 xff1a 1 3步为 xff1a 把Tomcat配置到eclipse中 4 7步为 xff1a 把新建的Web项目部署到Tomcat中并运行 最后一部分为 xff1a 改变Web工程的真实部署路径到Tomcat中目录下 x1f447
  • ROS与C++入门教程-消息-序列化和适配类型

    原文地址 xff1a ROS与C 43 43 入门教程 消息 序列化和适配类型 创客智造 说明 xff1a 介绍序列化和适配类型在C Turtle版本增加 序列化到内存 使用ros serialization serialize 函数 xf
  • 前方高能,官方教程:教你如何玩转 GitHub !

    回复 1024 xff0c 送你一个特别推送 作为程序员 xff0c 一般手上会有三把剑 xff0c 用好了这三把剑 xff0c 对于编程来说 xff0c 对于解决编程中遇到的 Bug xff0c 都应该能够轻而易举的解决 程序员手中的三把
  • 树莓派安装宝塔面板后无法连接VNC

    解决方法 xff1a 登陆宝塔面板后台 xff0c 在 安全 中放行端口5900即可
  • Python并行处理视频帧

    参考链接 xff1a Speedy Computer Vision Pipelines using Parallelism 方案 xff1a 使用Python多进程编程 xff0c 将视频分成多个小段 xff0c 可按照CPU核数num p
  • 深度学习目标检测之SSD

    经典论文SSD笔记 论文链接 xff1a SSD Single Shot MultiBox Detector论文报告 xff1a ssd eccv2016 slide目标检测百页综述 xff0c 从传统方法到深度学习 xff1a Objec
  • C++编译报错fmt未定义的引用

    对 fmt v5 internal basic data POWERS OF 10 64 未定义的引用 1 最简单的方法 xff1a 把代码中printf的输出全部换成std cout或者其他的 2 安装fmt包 git clone htt
  • PyG/torch_geometric的一些坑

    安装PyG span class token keyword import span os span class token keyword import span torch os span class token punctuation
  • Pixhawk官网飞行模式介绍

    欢迎交流 个人 Gitter 交流平台 xff0c 点击直达 xff1a Flight Mode 飞行模式 原文地址 http dev px4 io concept flight modes html 飞行模式定义了系统在任何给定时间的状态
  • PX4中文维基汉化项目启动

    欢迎交流 个人 Gitter 交流平台 xff0c 点击直达 xff1a 敬启者 xff1a 打算进行PX4官网的汉化工作 GitBook 与官网的方式相同 xff0c 我们也是将网站以GitBook的方式呈现给大家 汉化后的版本先点点点点
  • Windows / Ubuntu操作系统下Pixhawk原生固件PX4的编译方法

    欢迎交流 个人 Gitter 交流平台 xff0c 点击直达 xff1a 更新于2017 3 13 FAQ 本文说明针对 PX4 Firmware 1 6 0 问题 1 xff1a 找不到python jinja2模块 CMake Erro
  • Sublime Text中文乱码的解决方法

    Sublime Text Sublime Text这款代码编译器相当不错 xff0c 自带高亮显示 xff0c 界面清新 但是Sublime Text默认是不支持中文显示的 xff0c 这种中文乱码的行为万万是不能够接受的 这里简单介绍一下
  • 自制Pixhawk飞控板烧写BootLoader教程

    对于自己制作的飞控板 xff0c 通过USB连接电脑之后 xff0c 开始电脑是无法检测到飞控板的端口存在的 检测不到端口 xff0c 就不能用控制台给飞控板烧写固件 xff0c 就不能用QGroundControl xff0c 就不能进行
  • Pixhawk原生固件PX4之常用函数解读

    欢迎交流 个人 Gitter 交流平台 xff0c 点击直达 xff1a PX4Firmware 经常有人将Pixhawk PX4 APM还有ArduPilot弄混 这里首先还是简要说明一下 xff1a Pixhawk是飞控硬件平台 xff
  • Pixhawk原生固件PX4之添加uORB主题

    欢迎交流 个人 Gitter 交流平台 xff0c 点击直达 xff1a 本说明针对 Firmware v1 5 4 1 添加流程说明 1 在Firmware msg下新建uORB的成员变量 xff0c eg xxx msg 2 在Firm
  • Pixhawk原生固件PX4之SITL软件在环仿真

    欢迎交流 个人 Gitter 交流平台 xff0c 点击直达 xff1a 故事开始之前 xff0c 先按照笔者的这一篇博客在Ubuntu上完成固件的编译 jMAVSim仿真 jMAVSim仿真不需要任何配置 xff0c 直接输入指令即可 s
  • Pixhawk原生固件PX4之串口读取信息

    欢迎交流 个人 Gitter 交流平台 xff0c 点击直达 xff1a 这篇博客纯粹出于对FreeApe这位先行者贡献的复现 xff0c 也是本人一直想要进行的一项操作 在此还是做一下记录 时代在改变 xff0c 代码在更新 xff0c
  • Effective Modern C++ 条款21 比起直接使用new,更偏爱使用std::make_unique和std::make_shared

    比起直接使用new xff0c 更偏爱使用std make unique和std make shared 让我们从std make unique和std make shared之间的比较开始讲起吧 std make shared是C 43