ffmpeg 的 api 和 数据结构都是 c 风格,当我在 c++ 中使用它们时,很自然就想到用智能指针去管理(例如 AVFormatContext*
、AVCodecContext*
等),因为可以自定义删除器,将 ffmpeg 提供的 free
操作放进去;但 ffmpeg 中的一些 api 需要传入裸指针,一些 api 甚至会在内部直接分配空间,这样子用智能指针管理的想法会不会是没必要的?
拿 AVFormatContext
来举例,正常可以像这样得到一个被智能指针管理的 AVFormatContext
结构
auto deleter = [](AVFormatContext* f){ if(f) avformat_free_context(f); }; std::unique_ptr<AVFormatContext, decltype(deleter)> fmt(avformat_alloc_context(), deleter);
但和它相关的一个 api 是 avformat_open_input
,它的函数声明如下(以下贴出一部分实现)
int avformat_open_input(AVFormatContext **ps, const char *url, const AVInputFormat *fmt, AVDictionary **options); // demux.c 下 avformat_open_input 的一部分实现 int avformat_open_input(AVFormatContext **ps, const char *filename, const AVInputFormat *fmt, AVDictionary **options) { ... AVFormatContext *s = *ps; ... if (!s && !(s = avformat_alloc_context())) return AVERROR(ENOMEM); ... }
可以看到 avformat_open_input
需要一个二级指针,所以需要直接传入裸指针。如果想要将一个初始化的 unique_ptr<AVFormatContext>
搭配 avformat_open_input
使用,就需要像这样(网上看到的做法)
auto deleter = [](AVFormatContext* f){ if(f) avformat_free_context(f); }; std::unique_ptr<AVFormatContext, decltype(deleter)> fmt(avformat_alloc_context(), deleter); auto tmp = fmt.get(); avformat_open_input(&tmp, ...);
到这里我就开始怀疑用 unique_ptr
管理 AVFormatContext
的意义了,不过以上这个例子还好,只是观感上没那么优雅。但以下的例子让我质疑用智能指针做管理的必要。
AVFormatContext
还有一个相关的 api 是 avformat_alloc_output_context2
,以下是函数声明和部分实现:
int avformat_alloc_output_context2(AVFormatContext **ctx, const AVOutputFormat *oformat, const char *format_name, const char *filename); int avformat_alloc_output_context2(AVFormatContext **avctx, const AVOutputFormat *oformat, const char *format, const char *filename) { AVFormatContext *s = avformat_alloc_context(); int ret = 0; *avctx = NULL; ... *avctx = s; }
可以看到,avformat_alloc_output_context2
同样需要传入二级指针,但与 avformat_open_input
的区别在于,它内部直接将 *avctx = NULL
,并没有判断其是否为空,同时还将分配了新的内存地址给 avctx
,这也就意味着以下的操作会造成内存泄漏:
auto deleter = [](AVFormatContext* f){ if(f) avformat_free_context(f); }; std::unique_ptr<AVFormatContext, decltype(deleter)> fmt(avformat_alloc_context(), deleter); auto tmp = fmt.get(); avformat_alloc_output_context2(&tmp, ...);
至此让我产生用智能指针管理 ffmpeg 数据结构的必要性,有没有大佬来解答一下。
![]() | 1 codehz 79 天前 现在不是有很多 c++ wrapper 吗,ffmpeg 的 c 接口变化也不是很大,直接用现成的包装器就好了 |
![]() | 3 itechify PRO 把你原文复制给 ai ,他会很清晰的说明 |
![]() | 4 rainbowhu 79 天前 完全没必要。个人的观点是 c 风格的 api 直接用就行了,大不了封装一些功能类的时候写成 c++。如果只是把 c 的 api 转成 c++,没有任何增加的新内容,完全是给自己找麻烦了。c++的很多语法看着很爽,其实都是有固定场景的,场景不对就各种用着不爽,一直不喜欢这些花里胡哨的东西。 |
5 NessajCN 79 天前 确实没必要,手动 alloc free 吧 |
6 johnnyyeen 79 天前 写了 10 多年 C++代码,唯一生产实践中用类似智能指针机制的场景,是 COM/ATL 相关的开发。 从来没用过 C++的智能指针。 引入的复杂度比带来的好处多不了多少。 |
![]() | 7 qieqie 79 天前 先传入裸指针调`avformat_alloc_output_context2` 做内存分配, 再把所有权转移给 unique_ptr |
![]() | 8 wesleywaters 79 天前 ![]() RAII 原则要在资源生命周期与对象生命周期匹配时才 make sense ,你觉得别扭的原因就是因为你只是用智能指针声明了一个对象,但这个对象实际蕴含的资源生命周期和智能指针对象并不 match ,甚至其原生的管理接口与智能指针语义相悖。 合理的做法是大约有两种 1 、不用多纠结这个问题,C 风格的接口就对应 C 风格的调用方式,只是和项目种其他现代风格的代码模块做必要区隔即可。 2 、给 ffmpeg 写一个 wrapper ,以你说的 AVFormatContext 为例,可以在 ffmpeg api 的基础上提供一个新的封装类 A ,将 AVFormatContext 相关的资源与操作都封装在其中,确保 A 的构造、析构等方法正确管理底层资源。同时,业务代码只通过 A 来管理这些资源或调用这些操作。此时,你在业务代码中声明 A 的智能指针对象并进行管理就不会觉得别扭了,也符合 RAII 的真实语义。 |
![]() | 9 MIUIOS 79 天前 @oneisall8955 AI 不是万能的 |
![]() | 10 valord577 79 天前 以下仅个人工作相关的观点哈: 1. 如果是自有的产品 能完全掌控部署和编译环境 那么只要内部团队协商好就行。 2. 如果是提供 sdk 动态库给客户用,一定是提供 c 的 api 。原因有二,abi 稳定 客户可以用自己的编译器 / api 符号稳定。至于 sdk 内部语言用 c 还是 c++都无所谓 如果用 c++实现的 sdk 切记打包静态 c++运行时(libstdc++.a/libc++.a) 不然客户那边会有一大堆问题 |
11 aiyolo 79 天前 @johnnyyeen 不用智能指针,用 c++干吗呢 |
12 unused 79 天前 没看懂最后一个为什么会泄露。fmt 还是原来的 fmt ,最后 fmt.reset(tmp) 就行了 |
![]() | 13 msg7086 79 天前 C 风格指针你用 C++那套来管理就是会别扭的。 真要用 C++那套那你得把 C 指针那些丑陋的部分包装起来,但这样也就变成 1 楼说的那种了…… |
![]() | 14 minami 79 天前 via Android 如果你有做资源池的话可以用智能指针管理,如果没有的话,用 defer 的形式更方便 |
![]() | 15 ysc3839 79 天前 via Android 有必要。假如有个轻量的 FFmpeg C++封装库的话,我会使用。 但如果找不到现成的库,就比较尴尬了,我会看需求自己写。 顺带一提,你那么写 deleter 似乎会导致最终的 unique_ptr 变大。 @rainbowhu 不用智能指针的话,错误处理会麻烦很多,也有可能忘记释放。 |
16 kneep 79 天前 没有必要,直接用它原生的风格就好。C 语言的库,API 分配内存,使用者来释放,很常见,也不难理解。硬要用智能指针,会使代码更能难读。 |
![]() | 17 aminobody 79 天前 也许你更需要 scope_guard 这样的东西, 而不是智能指针. |
18 nightwitch 79 天前 这种可能会获取到资源的 allloc 函数,就先按 C 风格调,然后 reset 给一个 unique_ptr 管理就行了。 |
19 zcion OP 感谢各位大佬的意见 |
20 wnpllrzodiac 78 天前 via Android ffmpeg 用 c 实现了智能指针和其他 cpp 高级特性 |
![]() | 21 mirrorman 78 天前 通过智能指针管理资源的生命周期,让对象和资源的生命周期绑定,让资源的生命周期和有效性受到控制,至于在接口中传递的情况,使用裸指针、还是引用、还是仍旧使用智能指针,这反而是次要的,你仍然可以使用 unique_ptr 和 shared_ptr 的 get()接口来获得包裹的原始指针去与其他接口交互 |
![]() | 22 mirrorman 78 天前 而且建议在使用 unique_ptr 的时候,Deleter 以 factor 的方式定义: struct Deleter { void operator()(T* p) {//...} }; 这样构造的时候不需要再传入一个 Deleter 的实例,而且 unique_ptr 的内存占用跟裸指针就是一样大的;不然内部还会保存一个 Deleter 实例,unique_ptr 的内存占用会变大。 |
23 johnnyyeen 77 天前 @aiyolo 智能指针根本一点都不智能,而且同样也容易 out of control 。 |
24 johnnyyeen 77 天前 在我看来,智能指针无非就是为了应付针对指针的诟病,临时打的一个补丁,而且还没补好。 |