用智能指针管理 ffmpeg 中的数据结构是有必要的吗?

11 天前
 zcion

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 数据结构的必要性,有没有大佬来解答一下。

1930 次点击
所在节点    C++
24 条回复
codehz
11 天前
现在不是有很多 c++ wrapper 吗,ffmpeg 的 c 接口变化也不是很大,直接用现成的包装器就好了
zcion
11 天前
@codehz 能举例说一下吗,这一块不是很懂,谢谢
oneisall8955
11 天前
把你原文复制给 ai ,他会很清晰的说明
rainbowhu
11 天前
完全没必要。个人的观点是 c 风格的 api 直接用就行了,大不了封装一些功能类的时候写成 c++。如果只是把 c 的 api 转成 c++,没有任何增加的新内容,完全是给自己找麻烦了。c++的很多语法看着很爽,其实都是有固定场景的,场景不对就各种用着不爽,一直不喜欢这些花里胡哨的东西。
NessajCN
11 天前
确实没必要,手动 alloc free 吧
johnnyyeen
11 天前
写了 10 多年 C++代码,唯一生产实践中用类似智能指针机制的场景,是 COM/ATL 相关的开发。
从来没用过 C++的智能指针。
引入的复杂度比带来的好处多不了多少。
qieqie
11 天前
先传入裸指针调`avformat_alloc_output_context2` 做内存分配, 再把所有权转移给 unique_ptr
wesleywaters
11 天前
RAII 原则要在资源生命周期与对象生命周期匹配时才 make sense ,你觉得别扭的原因就是因为你只是用智能指针声明了一个对象,但这个对象实际蕴含的资源生命周期和智能指针对象并不 match ,甚至其原生的管理接口与智能指针语义相悖。

合理的做法是大约有两种——

1 、不用多纠结这个问题,C 风格的接口就对应 C 风格的调用方式,只是和项目种其他现代风格的代码模块做必要区隔即可。
2 、给 ffmpeg 写一个 wrapper ,以你说的 AVFormatContext 为例,可以在 ffmpeg api 的基础上提供一个新的封装类 A ,将 AVFormatContext 相关的资源与操作都封装在其中,确保 A 的构造、析构等方法正确管理底层资源。同时,业务代码只通过 A 来管理这些资源或调用这些操作。此时,你在业务代码中声明 A 的智能指针对象并进行管理就不会觉得别扭了,也符合 RAII 的真实语义。
totoro52
11 天前
@oneisall8955 AI 不是万能的
valord577
11 天前
以下仅个人工作相关的观点哈:


1. 如果是自有的产品 能完全掌控部署和编译环境 那么只要内部团队协商好就行。
2. 如果是提供 sdk 动态库给客户用,一定是提供 c 的 api 。原因有二,abi 稳定 客户可以用自己的编译器 / api 符号稳定。至于 sdk 内部语言用 c 还是 c++都无所谓 如果用 c++实现的 sdk 切记打包静态 c++运行时(libstdc++.a/libc++.a) 不然客户那边会有一大堆问题
aiyolo
10 天前
@johnnyyeen 不用智能指针,用 c++干吗呢
unused
10 天前
没看懂最后一个为什么会泄露。fmt 还是原来的 fmt ,最后 fmt.reset(tmp) 就行了
msg7086
10 天前
C 风格指针你用 C++那套来管理就是会别扭的。
真要用 C++那套那你得把 C 指针那些丑陋的部分包装起来,但这样也就变成 1 楼说的那种了……
minami
10 天前
如果你有做资源池的话可以用智能指针管理,如果没有的话,用 defer 的形式更方便
ysc3839
10 天前
有必要。假如有个轻量的 FFmpeg C++封装库的话,我会使用。
但如果找不到现成的库,就比较尴尬了,我会看需求自己写。

顺带一提,你那么写 deleter 似乎会导致最终的 unique_ptr 变大。

@rainbowhu 不用智能指针的话,错误处理会麻烦很多,也有可能忘记释放。
kneep
10 天前
没有必要,直接用它原生的风格就好。C 语言的库,API 分配内存,使用者来释放,很常见,也不难理解。硬要用智能指针,会使代码更能难读。
aminobody
10 天前
也许你更需要 scope_guard 这样的东西, 而不是智能指针.
nightwitch
10 天前
这种可能会获取到资源的 allloc 函数,就先按 C 风格调,然后 reset 给一个 unique_ptr 管理就行了。
zcion
10 天前
感谢各位大佬的意见
wnpllrzodiac
10 天前
ffmpeg 用 c 实现了智能指针和其他 cpp 高级特性

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/1146903

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX