一、RAII:资源获取即初始化 RAII(Resource Acquisition Is Initialization)是 C++ 最重要的惯用法之一。核心思想是:将资源的生命周期与对象的生命周期绑定——构造函数获取资源,析构函数释放资源 。这确保了即使在异常路径下,资源也能被正确释放。
Android 的 AOSP 代码中大量使用 RAII。以 Binder 驱动的锁管理为例:
class Mutex {public : int lock () ; void unlock () ; }; class AutoMutex {public : AutoMutex (Mutex& m) : mLock (m) { mLock.lock (); } ~AutoMutex () { mLock.unlock (); } private : Mutex& mLock; }; void IPCThreadState::waitForResponse (Parcel *reply, status_t *acquireResult) { AutoMutex _l(mProcess->mThreadCountLock); }
源码路径:frameworks/native/libs/binder/include/binder/Mutex.h
1.1 智能指针(Smart Pointers) C++11 标准库提供了三种智能指针,Android AOSP 还有自己的实现(sp/wp 即 StrongPointer/WeakPointer,基于轻量级引用计数)。
shared_ptr :共享所有权,引用计数为 0 时释放资源。
#include <memory> class Connection {public : Connection (const std::string& addr) : m_addr (addr) { } ~Connection () { } private : std::string m_addr; }; void processRequest () { auto conn = std::make_shared <Connection>("192.168.1.1:8080" ); doSomething (conn); }
unique_ptr :独占所有权,不可拷贝,可移动。零开销(与裸指针性能相同)。
std::unique_ptr<AudioTrack> createAudioTrack (int sampleRate) { auto track = std::make_unique <AudioTrack>(sampleRate); if (!track->init ()) { return nullptr ; } return track; }
weak_ptr :弱引用,不增加引用计数,用于打破 shared_ptr 的循环引用。
class Node { std::string name; std::vector<std::shared_ptr<Node>> children; std::weak_ptr<Node> parent; public : Node (const std::string& n) : name (n) {} void setParent (std::shared_ptr<Node> p) { parent = p; } };
1.2 Android AOSP 的 sp/wp Android 的 Binder IPC 体系使用 sp<T>(强指针)和 wp<T>(弱指针)管理引用计数:
class RefBase {public : void incStrong (const void * id) const ; void decStrong (const void * id) const ; }; template <typename T>class sp { T* m_ptr; public : sp (T* other) : m_ptr (other) { if (m_ptr) m_ptr->incStrong (this ); } ~sp () { if (m_ptr) m_ptr->decStrong (this ); } };
IBinder、BnInterface、BpInterface 等 Binder 核心类都继承自 RefBase,通过 sp 管理生命周期。
二、虚函数与多态机制 2.1 vtable 与 vptr 的底层原理 当一个类声明了虚函数,编译器会为这个类生成一张虚函数表(vtable) ,存储在只读数据段中。类的每个对象实例包含一个隐藏指针 vptr ,指向该类的 vtable。
class Base {public : virtual void f () { printf ("Base::f\n" ); } virtual void g () { printf ("Base::g\n" ); } int data; }; class Derived : public Base {public : void f () override { printf ("Derived::f\n" ); } virtual void h () { printf ("Derived::h\n" ); } };
虚函数调用的开销:需要两次间接寻址(读取 vptr → 读取 vtable 中的函数指针 → 调用)。这也是为什么虚函数不能被内联优化——编译器在编译时不知道实际调用的是哪个版本。
2.2 纯虚函数与抽象基类 class IAudioTrack {public : virtual ~IAudioTrack () = default ; virtual int write (const void * buffer, size_t size) = 0 ; virtual int flush () = 0 ; };
2.3 多重继承与虚继承 class Animal {public : int age; virtual void speak () = 0 ; }; class Mammal : public Animal { };class WingedAnimal : public Animal { };class Bat_NonVirtual : public Mammal, public WingedAnimal { };class Mammal_Virtual : virtual public Animal { };class WingedAnimal_Virtual : virtual public Animal { };class Bat : public Mammal_Virtual, public WingedAnimal_Virtual {public : void speak () override { } };
三、移动语义与右值引用 C++11 的移动语义是一个关键优化:对于临时对象,可以将资源”移动”而不是”拷贝”,避免不必要的内存分配。
class Buffer { char * m_data; size_t m_size; public : Buffer (Buffer&& other) noexcept : m_data (other.m_data), m_size (other.m_size) { other.m_data = nullptr ; other.m_size = 0 ; } Buffer& operator =(Buffer&& other) noexcept { if (this != &other) { delete [] m_data; m_data = other.m_data; m_size = other.m_size; other.m_data = nullptr ; other.m_size = 0 ; } return *this ; } Buffer (const Buffer&) = delete ; Buffer& operator =(const Buffer&) = delete ; };
在 Android NDK 开发中,大 Buffer(如相机帧数据、音频数据)通过移动语义传递可以避免不必要的数据拷贝,显著提升性能。
std::vector<uint8_t > frame1 = captureFrame (); std::vector<uint8_t > frame2 = std::move (frame1);
std::forward 用于完美转发,保持参数的左值/右值属性:
template <typename T>void wrapper (T&& arg) { target (std::forward<T>(arg)); }
3.1 Rule of Five 如果类需要自定义析构函数、拷贝构造或拷贝赋值中的任何一个,那么很可能需要全部五个(拷贝构造、拷贝赋值、移动构造、移动赋值、析构函数):
class ResourceManager {public : ResourceManager (); ~ResourceManager (); ResourceManager (const ResourceManager&); ResourceManager& operator =(const ResourceManager&); ResourceManager (ResourceManager&&) noexcept ; ResourceManager& operator =(ResourceManager&&) noexcept ; };
四、模板元编程 C++ 模板不仅是泛型编程的工具,还是一种编译期计算语言。Android 的 HAL(硬件抽象层)和 ART 运行时大量使用模板提升性能和复用。
4.1 SFINAE(替换失败不是错误) SFINAE 规则是模板元编程的基础:当模板参数替换失败时,编译器不会报错,而是从重载集中排除该模板。
template <typename T>class has_foo {private : typedef char yes[1 ]; typedef char no[2 ]; template <typename U, U> struct type_check ; template <typename C> static yes& test (type_check<void (C::*)(), &C::foo>*) ; template <typename > static no& test (...) ;public : static constexpr bool value = (sizeof (test <T>(nullptr )) == sizeof (yes)); };
C++17 引入 if constexpr 后,条件编译变得更简洁:
template <typename T>void serialize (std::vector<uint8_t >& buffer, const T& value) { if constexpr (std::is_integral_v<T>) { buffer.insert (buffer.end (), reinterpret_cast <const uint8_t *>(&value), reinterpret_cast <const uint8_t *>(&value) + sizeof (T)); } else if constexpr (std::is_same_v<T, std::string>) { uint32_t len = value.size (); serialize (buffer, len); buffer.insert (buffer.end (), value.begin (), value.end ()); } else { static_assert (sizeof (T) == 0 , "Unsupported type for serialization" ); } }
4.2 CRTP(奇异递归模板模式) CRTP 允许在编译期实现静态多态(无虚函数开销):
template <typename Derived>class Singleton {public : static Derived& getInstance () { static Derived instance; return instance; } protected : Singleton () = default ; ~Singleton () = default ; Singleton (const Singleton&) = delete ; Singleton& operator =(const Singleton&) = delete ; }; class AudioManager : public Singleton<AudioManager> { friend class Singleton <AudioManager>; private : AudioManager () = default ; public : void play () { } };
4.3 Type Traits 标准库的 type_traits 头文件提供了大量编译期类型判断和转换的工具:
#include <type_traits> static_assert (std::is_same_v<int , int32_t >); static_assert (std::is_base_of_v<Base, Derived>); static_assert (std::is_constructible_v<MyClass, int , double >); using Decayed = std::decay_t <const int &>; using Underlying = std::underlying_type_t <EnumClass>;
在 Android NDK 中的实际应用——类型安全的资源管理:
template <typename T>class sp { static_assert (std::is_base_of_v<RefBase, T>, "sp<T>: T must be a subclass of RefBase" ); };
4.4 变参模板(Variadic Templates) void safe_printf (const char * format) { while (*format) { if (*format == '%' ) { throw std::runtime_error ("Missing argument for format specifier" ); } putchar (*format++); } } template <typename T, typename ... Args>void safe_printf (const char * format, T&& value, Args&&... args) { while (*format) { if (*format == '%' && *(format + 1 ) != '%' ) { std::cout << std::forward<T>(value); safe_printf (format + 2 , std::forward<Args>(args)...); return ; } putchar (*format++); } }
五、Lambda 表达式 C++ lambda 是 Android NDK 开发中最常用的语法糖之一,广泛应用于回调、线程任务、STL 算法等场景。
auto byValue = [value]() { return value * 2 ; }; auto byRef = [&value]() { value *= 2 ; }; auto all = [=]() { return x + y + z; }; auto allRef = [&]() { x = y = z = 0 ; }; auto mixed = [=, &result]() { result = a + b; }; std::thread task ([buffer = std::move(buffer)]() { processBuffer(buffer); }) ;task.join (); std::vector<int > values = {5 , 3 , 1 , 4 , 2 }; std::sort (values.begin (), values.end (), [](int a, int b) { return a > b; }); auto it = std::find_if (values.begin (), values.end (), [threshold = 3 ](int x) { return x > threshold; });
注意:lambda 捕获引用时,必须确保 lambda 执行时引用仍然有效:
std::function<void () > createBadCallback () { int local = 42 ; return [&local]() { std::cout << local; }; } std::function<void () > createGoodCallback () { int local = 42 ; return [local]() { std::cout << local; }; }
六、STL 容器选择指南 Android NDK 开发中的容器选择直接影响性能。以下是关键容器的特性对比和选择建议:
容器
底层
随机访问
插入/删除
内存
适用场景
vector
动态数组
O(1)
O(n)
连续
默认首选,尾部操作
deque
分段数组
O(1)
O(1) 两端
分段
双端队列
list
双向链表
O(n)
O(1)
分散,每元素额外 2 指针
大量中间插入/删除
set/map
红黑树
O(log n)
O(log n)
分散
有序集合/映射
unordered_set/map
哈希表
O(1) 均摊
O(1) 均摊
分散,有桶开销
快速查找,不关心顺序
std::vector<Pixel> scanline; scanline.reserve (1920 ); std::unordered_map<std::string, std::unique_ptr<Plugin>> pluginRegistry; auto it = pluginRegistry.find ("video_decoder" );std::map<uint32_t , std::string> sortedLogs;
6.1 vector 的扩容策略 std::vector<int > v; v.reserve (100 ); v.shrink_to_fit ();
6.2 自定义分配器 template <typename T>class SharedMemoryAllocator {public : using value_type = T; SharedMemoryAllocator (int shm_fd) : m_fd (shm_fd) {} T* allocate (std::size_t n) { void * ptr = mmap (nullptr , n * sizeof (T), PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0 ); return static_cast <T*>(ptr); } void deallocate (T* p, std::size_t n) { munmap (p, n * sizeof (T)); } private : int m_fd; }; std::vector<int , SharedMemoryAllocator<int >> shared_vec ( SharedMemoryAllocator <int >(shm_fd));
七、placement new 与手动生命周期管理 placement new 允许在已分配的内存上构造对象,用于对象池、内存池等场景:
#include <new> template <typename T, size_t N>class ObjectPool { alignas (T) char m_storage[sizeof (T) * N]; bool m_used[N] = {}; public : T* allocate () { for (size_t i = 0 ; i < N; i++) { if (!m_used[i]) { m_used[i] = true ; return new (&m_storage[i * sizeof (T)]) T (); } } return nullptr ; } void deallocate (T* ptr) { size_t idx = (reinterpret_cast <char *>(ptr) - m_storage) / sizeof (T); if (idx < N && m_used[idx]) { ptr->~T (); m_used[idx] = false ; } } ~ObjectPool () { for (size_t i = 0 ; i < N; i++) { if (m_used[i]) { reinterpret_cast <T*>(&m_storage[i * sizeof (T)])->~T (); } } } };
八、异常安全保证 C++ 标准定义了三个级别的异常安全保证:
void basic_guarantee_example (std::vector<int >& v, int value) { v.push_back (value); } void strong_guarantee_example (std::vector<int >& v, int value) { std::vector<int > tmp = v; tmp.push_back (value); v.swap (tmp); } void swap (MyClass& other) noexcept { std::swap (m_data, other.m_data); }
8.1 noexcept 的作用 void mayThrow () ;void neverThrow () noexcept ;static_assert (noexcept (neverThrow ()));static_assert (!noexcept (mayThrow ()));class EfficientMovable {public : EfficientMovable (EfficientMovable&&) noexcept = default ; }; template <typename T>void swap (T& a, T& b) noexcept (noexcept (a = std::declval<T>())) { T tmp = a; a = b; b = tmp; }
九、线程安全与互斥锁 #include <mutex> #include <shared_mutex> class ThreadSafeCache { mutable std::shared_mutex m_mutex; std::unordered_map<int , Data> m_cache; public : void update (int key, Data value) { std::unique_lock<std::shared_mutex> lock (m_mutex) ; m_cache[key] = std::move (value); } std::optional<Data> get (int key) const { std::shared_lock<std::shared_mutex> lock (m_mutex) ; auto it = m_cache.find (key); if (it != m_cache.end ()) { return it->second; } return std::nullopt ; } }; std::mutex mtx1, mtx2; void safeTransfer () { std::scoped_lock lock (mtx1, mtx2) ; } class Singleton { static std::unique_ptr<Singleton> s_instance; static std::once_flag s_flag; public : static Singleton& getInstance () { std::call_once (s_flag, []() { s_instance.reset (new Singleton ()); }); return *s_instance; } };
源码参考:AOSP 的 system/core/libutils/include/utils/Mutex.h 中,Android 使用 pthread_mutex_t 而非 std::mutex(因为 AOSP 代码长期基于 C++11 之前的标准)。
十、C++11/14/17/20 关键特性速览
版本
关键特性
C++11
auto, lambda, move semantics, smart pointers, constexpr, nullptr, range-for, variadic templates, static_assert, thread, chrono
C++14
generic lambda, return type deduction, decltype(auto), make_unique, binary literals
C++17
if constexpr, structured bindings, inline variables, string_view, optional/variant/any, fold expressions, std::filesystem
C++20
concepts, ranges, coroutines, modules, std::format, span, jthread, barrier/latch, semaphore
在 Android NDK 中,C++14 是默认支持的最低版本(NDK r17+),C++17 可通过 -std=c++17 启用。C++20 的部分特性在 NDK r26+ 中得到支持。
十一、const 正确性 const 是 C++ 类型系统的核心部分,不仅是”不可变”的承诺,也帮助编译器优化和防止 bug:
class AudioBuffer { int16_t * m_data; size_t m_size; public : size_t size () const { return m_size; } const int16_t * data () const { return m_data; } int16_t * data () { return m_data; } void append (const AudioBuffer& other) ; };
十二、面试常问题目 Q1: shared_ptr 和 unique_ptr 的区别?什么场景用哪个?
unique_ptr 独占所有权,不能拷贝(只能移动),编译后与裸指针性能相同(零开销)。适合工厂函数返回、容器中管理对象、PIMPL 惯用法。shared_ptr 共享所有权,有引用计数开销(原子操作),适合多个所有者需要共享同一对象的场景。原则:优先用 unique_ptr,只有确实需要共享所有权时才用 shared_ptr。
Q2: 移动语义解决了什么问题?std::move 做了什么?
移动语义解决了不必要的深拷贝开销——对于包含动态分配资源的对象(如 std::vector、std::string),”移动”只需要复制指针(O(1)),而”拷贝”需要复制整个数据(O(n))。std::move 是一个无条件将左值转换为右值引用的类型转换(static_cast),它本身不移动任何东西,只是让编译器选择移动构造函数/移动赋值运算符而不是拷贝版本。
Q3: std::mutex 和 std::shared_mutex 的区别?
std::mutex 是排他锁(互斥锁),同一时刻只有一个线程能获取锁。std::shared_mutex 支持两种锁定模式:shared_lock(读锁,允许并发读)和 unique_lock(写锁,独占)。在读多写少的场景(如缓存访问),shared_mutex 可显著提高并发性能。Android 中 android:hardware:details:utils 命名空间下有类似实现。
Q4: 为什么在 Android NDK 中 vector 通常优于 list?
虽然 list 的理论插入/删除复杂度是 O(1)(vs vector 的 O(n)),但现代 CPU 的缓存架构使得连续内存的访问远快于指针跳转。vector 在连续内存中存储元素,CPU 预取(prefetch)可以高效工作;list 的每个节点分散在堆上,每次跳转都可能造成 cache miss。除非元素非常大(如 > 1KB)且频繁在中间插入/删除,否则 vector 通常更快。
Q5: 什么是 vtable?虚函数调用的开销有多大?
vtable 是编译器为每个包含虚函数的类生成的一张函数指针表(存储在只读数据段)。每个对象包含一个隐藏的 vptr 指针(8 字节在 64 位系统上)指向该类的 vtable。虚函数调用需要:(1) 通过对象获取 vptr;(2) 在 vtable 中查找函数指针;(3) 通过函数指针调用。这比普通函数调用多了两次内存访问(两次间接寻址),约 5-20 个 CPU 周期。更关键的性能影响是:虚函数阻止了编译器内联优化,因为编译时无法确定实际被调用的函数版本。
参考源码路径:
AOSP RefBase:system/core/libutils/include/utils/RefBase.h
AOSP StrongPointer:system/core/libutils/include/utils/StrongPointer.h
AOSP Mutex:system/core/libutils/include/utils/Mutex.h
C++ Standard Library:https://en.cppreference.com/w/
Android NDK 文档:https://developer.android.com/ndk/guides/cpp-support