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

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

image.png


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*有哪些弊端:


  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、C++17提供的std::any类型。

目录
相关文章
|
15天前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
1月前
|
存储 C++
c++的指针完整教程
本文提供了一个全面的C++指针教程,包括指针的声明与初始化、访问指针指向的值、指针运算、指针与函数的关系、动态内存分配,以及不同类型指针(如一级指针、二级指针、整型指针、字符指针、数组指针、函数指针、成员指针、void指针)的介绍,还提到了不同位数机器上指针大小的差异。
31 1
|
1月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
23 2
|
1月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
1月前
|
存储 C++ 索引
C++函数指针详解
【10月更文挑战第3天】本文介绍了C++中的函数指针概念、定义与应用。函数指针是一种指向函数的特殊指针,其类型取决于函数的返回值与参数类型。定义函数指针需指定返回类型和参数列表,如 `int (*funcPtr)(int, int);`。通过赋值函数名给指针,即可调用该函数,支持两种调用格式:`(*funcPtr)(参数)` 和 `funcPtr(参数)`。函数指针还可作为参数传递给其他函数,增强程序灵活性。此外,也可创建函数指针数组,存储多个函数指针。
|
2月前
|
编译器 C++
【C++核心】指针和引用案例详解
这篇文章详细讲解了C++中指针和引用的概念、使用场景和操作技巧,包括指针的定义、指针与数组、指针与函数的关系,以及引用的基本使用、注意事项和作为函数参数和返回值的用法。
35 3
|
1月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
2月前
|
安全 C++ 开发者
C++ 11新特性之shared_ptr
C++ 11新特性之shared_ptr
18 0
|
2月前
|
C++
C++(十八)Smart Pointer 智能指针简介
智能指针是C++中用于管理动态分配内存的一种机制,通过自动释放不再使用的内存来防止内存泄漏。`auto_ptr`是早期的一种实现,但已被`shared_ptr`和`weak_ptr`取代。这些智能指针基于RAII(Resource Acquisition Is Initialization)原则,即资源获取即初始化。RAII确保对象在其生命周期结束时自动释放资源。通过重载`*`和`-&gt;`运算符,可以方便地访问和操作智能指针所指向的对象。
|
2月前
|
C++
C++(九)this指针
`this`指针是系统在创建对象时默认生成的,用于指向当前对象,便于使用。其特性包括:指向当前对象,适用于所有成员函数但不适用于初始化列表;作为隐含参数传递,不影响对象大小;类型为`ClassName* const`,指向不可变。`this`的作用在于避免参数与成员变量重名,并支持多重串联调用。例如,在`Stu`类中,通过`this-&gt;name`和`this-&gt;age`明确区分局部变量与成员变量,同时支持链式调用如`s.growUp().growUp()`。