如何实现:C++类方法存在则包装为C函数,否则设为nullptr
如何检测C++类方法存在性并自动生成带NULL的C函数表
我需要将任意C类通用包装为固定结构的C函数表(该结构体是库的公共接口,无法修改),其中部分函数项可设为NULL,且NULL具备特殊行为。目标是编写模板函数自动生成这个C函数表:若C类没有对应C函数表中的方法,就将表项设为NULL;若存在对应方法,则用lambda包装该方法作为表项。请问如何实现方法存在性检测,并在包装lambda和NULL之间自动切换?
C接口定义
struct device_module_info { module_info info; status_t (*init_device)(void *driverCookie, void **_deviceCookie); void (*uninit_device)(void *deviceCookie); void (*device_removed)(void *deviceCookie); status_t (*open)(void *deviceCookie, const char *path, int openMode, void **_cookie); status_t (*close)(void *cookie); status_t (*free)(void *cookie); status_t (*read)(void *cookie, off_t pos, void *buffer, size_t *_length); status_t (*write)(void *cookie, off_t pos, const void *buffer, size_t *_length); status_t (*io)(void *cookie, io_request *request); status_t (*control)(void *cookie, uint32 op, void *buffer, size_t length); status_t (*select)(void *cookie, uint8 event, selectsync *sync); status_t (*deselect)(void *cookie, uint8 event, selectsync *sync); };
待包装的C++类示例
class DeviceHandleImpl: public ImplBase<DeviceHandle, DeviceHandleImpl, gDeviceImplClass> { public: status_t Close(); status_t Free(); status_t Read(off_t pos, void *buffer, size_t *_length); status_t Write(off_t pos, const void *buffer, size_t *_length); status_t IO(io_request *request); status_t Control(uint32 op, void *buffer, size_t length); status_t Select(uint8 event, selectsync *sync); status_t Deselect(uint8 event, selectsync *sync); }; device_module_info gDeviceImplClass = GetDeviceClass< gDeviceImplClass, gDriverImplClass, DeviceImpl, DeviceHandleImpl >({ .name = "testDevice" });
当前尝试的包装生成函数
template <const device_module_info& selfClass, const driver_module_info& driverClass, typename DeviceImpl, typename DeviceHandleImpl> constexpr device_module_info GetDeviceClass(const module_info& info) { const device_module_info cls = { .info = info, .init_device = [](void *driverCookie, void **_deviceCookie) { Device device; status_t res = DeviceImpl::InitDevice({&driverClass, driverCookie}, device); *_deviceCookie = device.fInst; return res; }, .uninit_device = [](void *deviceCookie) { return static_cast<DeviceImpl*>(deviceCookie)->UninitDevice(); }, .device_removed = [](void *deviceCookie) { return static_cast<DeviceImpl*>(deviceCookie)->DeviceRemoved(); }, .open = [](void *deviceCookie, const char *path, int openMode, void **_cookie) { DeviceHandle handle; status_t res = static_cast<DeviceImpl*>(deviceCookie)->Open(path, openMode, handle); *_cookie = handle.fInst; return res; }, .close = [](void *cookie) { return static_cast<DeviceHandleImpl*>(cookie)->Close(); }, .free = [](void *cookie) { return static_cast<DeviceHandleImpl*>(cookie)->Free(); }, // `DeviceHandleImpl::Read` 方法可能不存在,这种情况下如何将表项设为 `nullptr`? .read = [](void *cookie, off_t pos, void *buffer, size_t *_length) { return static_cast<DeviceHandleImpl*>(cookie)->Read(pos, buffer, _length); }, .write = [](void *cookie, off_t pos, const void *buffer, size_t *_length) { return static_cast<DeviceHandleImpl*>(cookie)->Write(pos, buffer, _length); }, .io = [](void *cookie, io_request *request) { return static_cast<DeviceHandleImpl*>(cookie)->IO(request); }, .control = [](void *cookie, uint32 op, void *buffer, size_t length) { return static_cast<DeviceHandleImpl*>(cookie)->Control(op, buffer, length); }, .select = [](void *cookie, uint8 event, selectsync *sync) { return static_cast<DeviceHandleImpl*>(cookie)->Select(event, sync); }, .deselect = [](void *cookie, uint8 event, selectsync *sync) { return static_cast<DeviceHandleImpl*>(cookie)->Deselect(event, sync); }, }; return cls; }
解决方案
1. 方法存在性检测
针对每个需要包装的方法,先检测目标C类是否存在匹配签名的方法,根据C版本选择以下方式:
C++20及以上(推荐,更简洁)
用concept直接校验方法签名:
#include <concepts> // 检测T是否有符合签名的Read方法 template<typename T> concept HasRead = requires(T* obj, off_t pos, void* buf, size_t* len) { { obj->Read(pos, buf, len) } -> std::same_as<status_t>; }; // 检测T是否有符合签名的Write方法 template<typename T> concept HasWrite = requires(T* obj, off_t pos, const void* buf, size_t* len) { { obj->Write(pos, buf, len) } -> std::same_as<status_t>; }; // 检测T是否有符合签名的Close方法 template<typename T> concept HasClose = requires(T* obj) { { obj->Close() } -> std::same_as<status_t>; }; // 同理为free、io、control等方法添加对应的concept
C11/C17版本(SFINAE方式)
用std::void_t实现SFINAE检测:
#include <type_traits> template<typename T, typename = void> struct HasRead : std::false_type {}; template<typename T> struct HasRead<T, std::void_t<decltype(std::declval<T>().Read(std::declval<off_t>(), std::declval<void*>(), std::declval<size_t*>()))>> : std::true_type {}; template<typename T> constexpr bool HasRead_v = HasRead<T>::value; // 检测Write方法 template<typename T, typename = void> struct HasWrite : std::false_type {}; template<typename T> struct HasWrite<T, std::void_t<decltype(std::declval<T>().Write(std::declval<off_t>(), std::declval<const void*>(), std::declval<size_t*>()))>> : std::true_type {}; template<typename T> constexpr bool HasWrite_v = HasWrite<T>::value; // 同理为free、io、control等方法添加检测结构体
2. 生成函数指针的辅助函数
针对每个C函数表项,编写辅助函数,根据类是否有对应方法返回包装lambda或nullptr:
C++20版本
template<typename T> constexpr auto MakeReadFunc() { if constexpr (HasRead<T>) { return [](void* cookie, off_t pos, void* buffer, size_t* _length) -> status_t { return static_cast<T*>(cookie)->Read(pos, buffer, _length); }; } else { return nullptr; } } // 实现Write方法的辅助函数 template<typename T> constexpr auto MakeWriteFunc() { if constexpr (HasWrite<T>) { return [](void* cookie, off_t pos, const void* buffer, size_t* _length) -> status_t { return static_cast<T*>(cookie)->Write(pos, buffer, _length); }; } else { return nullptr; } } // 实现Close方法的辅助函数 template<typename T> constexpr auto MakeCloseFunc() { if constexpr (HasClose<T>) { return [](void* cookie) -> status_t { return static_cast<T*>(cookie)->Close(); }; } else { return nullptr; } } // 同理实现free、io、control等方法的辅助函数
C++17版本
template<typename T> constexpr auto MakeReadFunc() { if constexpr (HasRead_v<T>) { return static_cast<status_t(*)(void*, off_t, void*, size_t*)>( [](void* cookie, off_t pos, void* buffer, size_t* _length) -> status_t { return static_cast<T*>(cookie)->Read(pos, buffer, _length); } ); } else { return static_cast<status_t(*)(void*, off_t, void*, size_t*)>(nullptr); } } // 同理实现其他方法的辅助函数,注意强制转换为对应函数指针类型
C++11版本
用SFINAE重载实现:
template<typename T, std::enable_if_t<HasRead_v<T>, int> = 0> constexpr status_t(*MakeReadFunc())(void*, off_t, void*, size_t*) { return [](void* cookie, off_t pos, void* buffer, size_t* _length) -> status_t { return static_cast<T*>(cookie)->Read(pos, buffer, _length); }; } template<typename T, std::enable_if_t<!HasRead_v<T>, int> = 0> constexpr status_t(*MakeReadFunc())(void*, off_t, void*, size_t*) { return nullptr; } // 同理实现其他方法的辅助函数
3. 修改包装生成函数
将原来的硬编码lambda替换为调用辅助函数的结果:
template <const device_module_info& selfClass, const driver_module_info& driverClass, typename DeviceImpl, typename DeviceHandleImpl> constexpr device_module_info GetDeviceClass(const module_info& info) { return { .info = info, .init_device = [](void* driverCookie, void** _deviceCookie) { Device device; status_t res = DeviceImpl::InitDevice({&driverClass, driverCookie}, device); *_deviceCookie = device.fInst; return res; }, .uninit_device = [](void* deviceCookie) { static_cast<DeviceImpl*>(deviceCookie)->UninitDevice(); }, .device_removed = [](void* deviceCookie) { static_cast<DeviceImpl*>(deviceCookie)->DeviceRemoved(); }, .open = [](void* deviceCookie, const char* path, int openMode, void** _cookie) { DeviceHandle handle; status_t res = static_cast<DeviceImpl*>(deviceCookie)->Open(path, openMode, handle); *_cookie = handle.fInst; return res; }, .close = MakeCloseFunc<DeviceHandleImpl>(), .free = MakeFreeFunc<DeviceHandleImpl>(), .read = MakeReadFunc<DeviceHandleImpl>(), .write = MakeWriteFunc<DeviceHandleImpl>(), .io = MakeIOFunc<DeviceHandleImpl>(), .control = MakeControlFunc<DeviceHandleImpl>(), .select = MakeSelectFunc<DeviceHandleImpl>(), .deselect = MakeDeselectFunc<DeviceHandleImpl>(), }; }
注意事项
- 确保C++类方法的签名与C函数指针完全匹配,包括返回值、参数类型(如
const修饰符),否则检测会失败。 - C++11中只有无捕获lambda能转换为函数指针,这里的lambda均为无捕获,符合要求。
- 优先使用C++20的
concept方式,代码更直观简洁;若需兼容旧版本,选择SFINAE实现。
内容的提问来源于stack exchange,提问作者X512
相关产品推荐
相关产品推荐

