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