写点什么

C++11 智能指针之 shared_ptr<void>

作者:轻口味
  • 2022 年 4 月 08 日
  • 本文字数:2799 字

    阅读完需:约 9 分钟

C++11 智能指针之shared_ptr<void>

1. 背景

基于 Alexa 的全链路智能语音 SDK 基于 C++实现了跨平台特性,跑通了 Android、Mac、Linux 等设备,在兼容 iOS 时发现 iOS 未提供音频采集和播放的 C++接口,所以需要改造 SDK,允许 SDK 初始化时注入外部的采集器和播放器实现类,同时 SDK 中的 Android 播放器是基于 ffmpeg 解码 + opensl 实现,但是考虑到包体积的问题,准备也基于这个接口在外部实现基于 Android 硬件解码的播放器。

2. 实现思路

在 SDK 内部定义了 ExternalMediaPlayerInterface 和 ExternalMicrophoneInterface 两个接口,初始化 SDK 时传入这两个对象:


int create_and_run_home_speech_core_engine(std::string& configFiles, \                                           std::string& configJsonData, \                                           std::shared_ptr<HomeSpeech::engine_result_t> engineResult, \                                           const std::string pathToKWDInputFolder = "",     \                                           const std::string& logLevel = "",                       std::shared_ptr<HomeSpeech::ExternalMicrophoneInterface> externalMicWrapper = nullptr,                       std::function<std::shared_ptr<HomeSpeech::ExternalMediaPlayerInterface>(std::shared_ptr<alexaClientSDK::avsCommon::sdkInterfaces::HTTPContentFetcherInterfaceFactoryInterface> contentFetcherFactory,                                                           bool enableEqualizer,                                                           const std::string& name)> createExternalMediaPlayerCallback = nullptr);
复制代码


由于两个接口依赖 SDK 内部的 AudioInputStream 数据结构,所以我们这里面使用了一个回调函数,在 SDK 内部中调用该方法,SDK 外部实现方法来创建具体的播放器。

3. Android 和 iOS 接口实现差异问题

本来这样实现已经够了,但是 Android 的采集和播放要使用同一个 opensl 对象,而该对象在 SDK 内部创建好了,复用的话需要 SDK 内部调用一个方法把 opensl 对象设置到播放器中,但是这个对象 iOS 并不需要,怎么办呢?


按照纯 C 指针的思路,接口定义成设置一个void *,C++中是允许裸指针,因此裸指针之间转换方法同 C 语言指针强转,但是整个工程都是基于 C++ 11 的智能指针,智能指针怎么转呢?先回顾一下 C++ 11 智能指针。

3.1 std::shared_ptr 类型强转 std::dynamic_pointer_cast

C++11 中引入了智能指针std::shared_ptr等,智能指针转换不能通过 C 方式进行强转,必须通过库提供转换函数进行转换。C++11 的方法是:std::dynamic_pointer_cast,如下代码所示:


#include <memory>#include <iostream>
class A { public: AA(){} virtual ~A() {}};
class B : public A { public: B(){} virtual ~B() {}};
int main(){ // derived class to A class B* d1 = new B(); A* b1 = d1; // std::shared_ptr<B> d2 = std::make_shared<B>(); std::shared_ptr<A> b2 = d2; /* * dynamic cast maybe failed. and return null; * */ B* d11 = dynamic_cast<B*>(b1); //succ B* d12 = static_cast<B*>(b1); //succ typedef std::shared_ptr<B> d_ptr; // std::shared_ptr<B> d21 = dynamic_cast<d_ptr>(b2); //compile error std::shared_ptr<B> d22 = std::dynamic_pointer_cast<B>(b2); return 0;}
复制代码


我们看看 dynamic_pointer_cast 与 dynamic_cast 的区别


dynamic_cast


将一个基类对象指针(或引用)cast 到继承类指针,dynamic_cast 会根据基类指针是否真正指向继承类指针来做相应处理。


主要用途:将基类的指针或引用安全地转换成派生类的指针或引用,并用派生类的指针或引用调用非虚函数。如果是基类指针或引用调用的是虚函数无需转换就能在运行时调用派生类的虚函数。


转换方式:


  • dynamic_cast< type* >(e) type 必须是一个类类型且必须是一个有效的指针

  • dynamic_cast< type& >(e) type 必须是一个类类型且必须是一个左值

  • dynamic_cast< type&& >(e) type 必须是一个类类型且必须是一个右值


e 的类型必须符合以下三个条件中的任何一个:


  1. e 的类型是目标类型 type 的公有派生类

  2. e 的类型是目标 type 的共有基类

  3. e 的类型就是目标 type 的类型。


如果一条 dynamic_cast 语句的转换目标是指针类型并且转换失败了,会返回一个空指针,则判断条件为 0,即为 false;如果转换成功,指针为非空,则判断条件为非零,即 true。


dynamic_pointer_cast 与 dynamic_cast 用法类似,当指针是智能指针时候,向下转换,用 dynamic_Cast 则编译不能通过,此时需要使用 dynamic_pointer_cast。

3.2 std::shared_ptr<void>

类似于void *想到了 std::shared_ptr<void>,了解了一下还真有。先看看直接使用void*有哪些弊端:


  1. void*不能保证类型安全,可以将一个void * 赋给 People*,无论它指向的对象是否实际上是 People 类的;

  2. void *不能像智能指针那样管理生命周期,因此必须手动管理关联数据的生命周期,容易导致内存泄漏;

  3. 库无法复制void *指向的对象,因为它不知道对象的类型


使用shared_ptr<void>代替void*可以解决声明周期管理的问题。shared_ptr 有足够的类型信息以了解如何正确销毁它指向的对象。但是 std::shared_ptr 和 void*一样不能解决类型安全的问题。


最后在使用了shared_ptr<void>在 SDK 内部进行类型强转时报错:


/Library/android-ndk-r17c/sources/cxx-stl/llvm-libc++/include/memory:4851:16: error: 'void' is not a class    Tp* __p = dynamic_cast<Tp*>(r.get());               ^                  ~ (~)~/xxx/src/main/cpp/AndroidMediaPlayer.cpp:494:19: note: in instantiation of function template specialization 'std::ndk1::dynamic_pointer_cast<alexaClientSDK::applicationUtilities::androidUtilities::AndroidSLESEngine, void>' requested here  m_engine = std::dynamic_pointer_castalexaClientSDK::applicationUtilities::androidUtilities::AndroidSLESEngine(engine);                  ^1 error generated.
复制代码

3.3 std::any

又了解了一下找到 std::any 这么一个类型,但是得 c++17 才可以使用。


定义在 any 头文件中:#include <any>,是一个可用于任何类型单个值的类型安全的容器.std: any 是一种值类型,它能够更改其类型,同时仍然具有类型安全性。也就是说,对象可以保存任意类型的值,但是它们知道当前保存的值是哪种类型。在声明此类型的对象时,不需要指定可能的类型。可以使用 any_cast<该值的类型>获取值。


最后还是在 SDK 内部实现了 AndoridExternalMediaplayerInterface 来适配 Android 平台。

4. 总结

本文基于项目实战介绍了 C++11 智能指针的类型转换 std::dynamic_pointer_cast,以及特殊的智能指针 std::shared_ptr<void>、C++17 提供的 std::any 类型。

发布于: 刚刚阅读数: 2
用户头像

轻口味

关注

🏆2021年InfoQ写作平台-签约作者 🏆 2017.10.17 加入

Android、音视频、AI相关领域从业者。 欢迎加我微信wodekouwei拉您进InfoQ音视频沟通群 邮箱:qingkouwei@gmail.com

评论

发布
暂无评论
C++11 智能指针之shared_ptr<void>_c++_轻口味_InfoQ写作平台