当前位置: 首页 > news >正文

C++编译期间验证单个对象可以被释放、验证数组可以被释放和验证函数对象能否被指定类型参数调用

目录

1.核心原理

1.1.SFINAE

1.2.SFINAE 的核心工具

1.2.1.decltype 与逗号表达式

1.2.2.void_t(C++17 引入)

1.2.3.模板参数的 “替换约束”

1.3.SFINAE 的典型应用场景

2.编译期间验证单个对象可以被释放

3.编译期间验证数组可以被释放

4.验证函数对象能否被指定类型参数调用

5.实际用途

6.总结


1.核心原理

1.1.SFINAE

        在 C++ 中,SFINAE 是 Substitution Failure Is Not An Error(替换失败不是错误)的缩写,是模板元编程中一项核心特性。它的核心思想是:当模板参数替换过程中出现无效代码时,编译器不会将其视为错误,而是忽略该模板特化 / 重载版本,继续寻找其他合法的候选版本

        模板在实例化时,编译器会尝试将实际参数 “替换” 进模板参数中。如果替换后出现语法或语义错误(如访问不存在的成员、调用不匹配的函数等),SFINAE 确保编译器不会直接报错,而是跳过这个无效的模板版本,选择其他可行的版本。

        只有当所有模板版本都替换失败时,编译器才会报错。

1.2.SFINAE 的核心工具

SFINAE 通常结合以下工具实现更灵活的类型检查:

1.2.1.decltype 与逗号表达式

C++惯用法: 通过std::decltype来SFINAE掉表达式

decltype(expression) 用于推导表达式的类型,若表达式无效则导致替换失败。逗号表达式 (expr1, expr2) 的结果为 expr2 的类型,可用于 “先检查 expr1 合法性,再返回 expr2 类型”。

示例:假设我们要实现两个函数模板,分别处理 “有 size() 成员的类型” 和 “普通类型”:

#include <iostream>
#include <vector>// 版本1:处理有 size() 成员的类型(通过 SFINAE 启用)
template <class T>
auto print_size(T& t) -> decltype(t.size(), void()) {  // 若 t.size() 合法,则此版本有效std::cout << "size: " << t.size() << std::endl;
}// 版本2:处理无 size() 成员的类型(默认版本)
template <class T>
void print_size(T& t) {std::cout << "no size() member" << std::endl;
}int main() {std::vector<int> vec(5);int num = 10;print_size(vec);  // 调用版本1:vec 有 size(),替换成功print_size(num);  // 调用版本2:num 无 size(),版本1替换失败,选择版本2return 0;
}

decltype(t.size(), void()) 表示:

  • 先检查 t.size() 是否合法(若不合法,替换失败);
  • 若合法,返回 void 类型(与函数返回值匹配)。

1.2.2.void_t(C++17 引入)

C++17之std::void_t

void_t 是一个辅助模板,定义为:

template <class... Args>
using void_t = void;  // 将任意类型列表转换为 void

它的作用是将 “表达式合法性检查” 转化为模板参数的替换问题。若 void_t<...> 中的表达式无效,则模板特化被丢弃。

示例:检查类型是否有 value 成员

#include <type_traits>// 基础模板:默认无 value 成员
template <class T, class = void>
struct has_value : std::false_type {};// 特化模板:若 T::value 存在,则匹配此版本
template <class T>
struct has_value<T, std::void_t<decltype(T::value)>> : std::true_type {};// 测试
struct A { static const int value = 10; };
struct B {};static_assert(has_value<A>::value, "A 有 value 成员");  // 成功
static_assert(!has_value<B>::value, "B 无 value 成员"); // 成功
  • 对 A 而言,decltype(A::value) 合法,void_t<...> 为 void,特化模板被选中(true_type)。
  • 对 B 而言,decltype(B::value) 无效,特化模板替换失败,使用基础模板(false_type)。

推荐阅读:

C++反射之检测struct或class是否实现指定函数

1.2.3.模板参数的 “替换约束”

通过在模板参数中添加约束条件(如检查类型是否派生自某个基类、是否满足特定接口),利用 SFINAE 过滤无效版本。

示例:检查类型是否派生自 Base

struct Base {};
struct Derived : Base {};
struct Other {};// 仅当 T 派生自 Base 时,此模板才有效
template <class T, class = std::enable_if_t<std::is_base_of_v<Base, T>>>
void func(T) {std::cout << "T 是 Base 的派生类" << std::endl;
}// 其他类型的重载
template <class T, class = std::enable_if_t<!std::is_base_of_v<Base, T>>>
void func(T) {std::cout << "T 不是 Base 的派生类" << std::endl;
}int main() {func(Derived());  // 调用第一个版本(Derived 派生自 Base)func(Other());    // 调用第二个版本(Other 不派生自 Base)return 0;
}

这里使用 std::enable_if_t(基于 SFINAE 实现),当条件为 true 时,enable_if_t 为 void(匹配模板参数),否则替换失败。

C++之std::enable_if

1.3.SFINAE 的典型应用场景

1.类型萃取(Type Traits)

标准库中的 std::is_pointerstd::has_virtual_destructor 等类型萃取工具,本质是通过 SFINAE 检查类型的特性。

C++模板编程之类型萃取

2.函数重载决议

根据类型的特性(如是否有某个成员、是否为算术类型)自动选择合适的函数版本,如本文开头的 print_size 例子。

C++模板函数重载规则细说

3.约束模板参数

在智能指针(如 shared_ptr)、容器等模板中,通过 SFINAE 确保传入的参数满足特定条件(如本文之前提到的 _Can_scalar_delete 检查指针能否被 delete 释放)。

4.模拟 “概念”(Concepts)

C++20 之前,SFINAE 是实现 “类型约束” 的主要方式(C++20 引入的 Concepts 是更直观的替代方案,但底层仍依赖 SFINAE 的思想)。

2.编译期间验证单个对象可以被释放

先上代码:

// 基础模板:默认不可用 scalar delete,继承 false_type
template <class _Yty, class = void>
struct _Can_scalar_delete : false_type {};// 特化模板:若 delete _Yty* 合法,则继承 true_type(排除 void 类型)
template <class _Yty>
struct _Can_scalar_delete<_Yty, void_t<decltype(delete _STD declval<_Yty*>())>> : bool_constant<!is_void_v<_Yty>> {};

不明白std::declval的可参考:

C++之std::declval

用于判断:对于类型 _Ytydelete _Yty* 操作是否合法(即能否用 scalar delete 释放单个对象)。

  • 基础模板:默认情况下,假设 delete _Yty* 不合法,继承 std::false_type(值为 false)。
  • 特化模板
    • 第二个模板参数是 void_t<decltype(...)>,其中 decltype(delete _STD declval<_Yty*>()) 用于检查 delete (Yty*) 表达式是否合法(能否通过编译)。
    • 若表达式合法(即 _Yty* 可以被 delete 释放),则特化模板被选中,且继承 bool_constant<!is_void_v<_Yty>>—— 排除 _Yty = void(因为 delete void* 是 C++ 未定义行为,不允许)。

示例:

// int* 可以被 delete 释放,且 _Yty 不是 void → 结果为 true
static_assert(_Can_scalar_delete<int>::value, "int* 可被 scalar delete"); // void* 不能被 delete 释放 → 结果为 false
static_assert(!_Can_scalar_delete<void>::value, "void* 不可被 scalar delete"); // 数组类型的指针(如 int[])用 scalar delete 不合法(应使用 delete[])→ 结果为 false
static_assert(!_Can_scalar_delete<int[]>::value, "数组指针不可用 scalar delete"); 

3.编译期间验证数组可以被释放

先上代码:

// 基础模板:默认不可用 array delete,继承 false_type
template <class _Yty, class = void>
struct _Can_array_delete : false_type {};// 特化模板:若 delete[] _Yty* 合法,则继承 true_type
template <class _Yty>
struct _Can_array_delete<_Yty, void_t<decltype(delete[] _STD declval<_Yty*>())>> : true_type {};

用于判断:对于类型 _Ytydelete[] _Yty* 操作是否合法(即能否用 array delete 释放数组对象)。

  • 基础模板:默认假设 delete[] _Yty* 不合法,继承 std::false_type
  • 特化模板:若 delete[] (Yty*) 表达式合法(如数组指针 int* 可用 delete[] 释放),则特化模板被选中,继承 std::true_type(值为 true)。

示例:

// int* 作为数组指针(new int[10])可用 delete[] 释放 → 结果为 true
static_assert(_Can_array_delete<int>::value, "int* 数组可被 array delete"); // 不完全类型(如前向声明的类)的数组指针可能无法 delete[] → 结果为 false
struct Incomplete; 
static_assert(!_Can_array_delete<Incomplete>::value, "不完全类型数组不可用 array delete"); 

4.验证函数对象能否被指定类型参数调用

先上代码:

// 基础模板:默认不可调用,继承 false_type
template <class _Fx, class _Arg, class = void>
struct _Can_call_function_object : false_type {};// 特化模板:若 _Fx 可被 _Arg 类型参数调用,则继承 true_type
template <class _Fx, class _Arg>
struct _Can_call_function_object<_Fx, _Arg, void_t<decltype(_STD declval<_Fx>()(_STD declval<_Arg>()))>> : true_type {};

用于判断:函数对象 _Fx 能否被调用,且参数类型为 _Arg(例如,自定义删除器能否接收资源指针作为参数)。

  • 基础模板:默认假设 _Fx 不能被 _Arg 类型参数调用,继承 std::false_type
  • 特化模板:若表达式 _Fx()(_Arg) 合法(即函数对象 _Fx 可以接收 _Arg 类型的参数并调用),则特化模板被选中,继承 std::true_type

示例:

// 自定义删除器:接收 int* 参数
auto int_deleter = [](int* p) { delete p; }; 
// 检查:int_deleter 能否被 int* 调用 → 结果为 true
static_assert(_Can_call_function_object<decltype(int_deleter), int*>::value, "int_deleter 可调用");// 字符串删除器:接收 const char* 参数
struct StrDeleter { void operator()(const char* p) { delete[] p; } };
// 检查:StrDeleter 能否被 int* 调用 → 结果为 false(参数类型不匹配)
static_assert(!_Can_call_function_object<StrDeleter, int*>::value, "StrDeleter 不可接收 int*");

5.实际用途

这些结构体在 shared_ptr 的构造函数中用于编译期验证,确保传入的指针或删除器符合要求:

1.在 shared_ptr 用原始指针构造时(如 shared_ptr<int>(new int)),通过 _Can_scalar_delete 确保 int* 可被 delete 释放,否则编译报错。

2.在 shared_ptr 管理数组时(C++17 及以上),通过 _Can_array_delete 确保数组指针可被 delete[] 释放。

 template <class _Ux,enable_if_t<conjunction_v<conditional_t<is_array_v<_Ty>, _Can_array_delete<_Ux>, _Can_scalar_delete<_Ux>>,_SP_convertible<_Ux, _Ty>>,int> = 0>explicit shared_ptr(_Ux* _Px) { // construct shared_ptr object that owns _Pxif constexpr (is_array_v<_Ty>) {_Setpd(_Px, default_delete<_Ux[]>{});} else {_Temporary_owner<_Ux> _Owner(_Px);_Set_ptr_rep_and_enable_shared(_Owner._Ptr, new _Ref_count<_Ux>(_Owner._Ptr));_Owner._Ptr = nullptr;}}

3.在 shared_ptr 传入自定义删除器时(如 shared_ptr<FILE>(fp, fclose)),通过 _Can_call_function_object 确保删除器可接收 FILE* 类型参数,否则编译报错。

template <class _Ux, class _Dx,enable_if_t<conjunction_v<is_move_constructible<_Dx>, _Can_call_function_object<_Dx&, _Ux*&>,_SP_convertible<_Ux, _Ty>>,int> = 0>
shared_ptr(_Ux* _Px, _Dx _Dt) { // construct with _Px, deleter_Setpd(_Px, _STD move(_Dt));
}template <class _Ux, class _Dx, class _Alloc,enable_if_t<conjunction_v<is_move_constructible<_Dx>, _Can_call_function_object<_Dx&, _Ux*&>,_SP_convertible<_Ux, _Ty>>,int> = 0>
shared_ptr(_Ux* _Px, _Dx _Dt, _Alloc _Ax) { // construct with _Px, deleter, allocator_Setpda(_Px, _STD move(_Dt), _Ax);
}template <class _Dx,enable_if_t<conjunction_v<is_move_constructible<_Dx>, _Can_call_function_object<_Dx&, nullptr_t&>>, int> = 0>
shared_ptr(nullptr_t, _Dx _Dt) { // construct with nullptr, deleter_Setpd(nullptr, _STD move(_Dt));
}

6.总结

这三个模板结构体是 SFINAE 技术的典型应用,用于在编译期检查特定操作(deletedelete[]、函数调用)的合法性:

  • _Can_scalar_delete:验证 delete _Yty* 是否合法(单个对象释放)。
  • _Can_array_delete:验证 delete[] _Yty* 是否合法(数组释放)。
  • _Can_call_function_object:验证函数对象能否被指定类型参数调用(如删除器兼容性)。

它们在智能指针等模板库中用于增强类型安全,将运行时错误提前到编译期暴露。

http://www.dtcms.com/a/578029.html

相关文章:

  • 机器学习训练过程中的回调函数BaseCallback
  • Cordys CRM正式开源,AI驱动客户关系管理加速演进
  • 河北省 建设执业注册中心网站长沙 汽车 网站建设
  • 手机如何定位:从时间差到地图上的“小蓝点”
  • Rust : Send、Sync与现实世界的映射
  • PHP推荐权重算法以及分页
  • 做软件赚钱的网站有哪些淘宝客seo推广教程
  • 企业网站制作建设建设通app官方下载
  • 【FAQ】HarmonyOS SDK 闭源开放能力 — Form Kit
  • 学习:JavaScript(8)
  • Docker的host网络模式
  • HORIBA 新型便携式废气测量系统技术解析
  • 建设自有网站需要什么杭州网站建设设计公司哪家好
  • 常州网站建设方案维护小皮搭建本地网站
  • 静态路由-等价路由、浮动路由配置
  • 37-38 for循环
  • 【SSM 框架 | day27 spring MVC】
  • H618-配置静态IP
  • 全面解析网站建设及报价高端网约车有哪些平台
  • 商城 静态网站模板wordpress 作品集插件
  • 论文分享 |用线性复杂度实现Transformer级性能的递归网络新范式
  • 12_FastMCP 2.x 中文文档之FastMCP高级功能:图标详解
  • 打工人日报#20251106
  • 在Windows上通过WSL体验openEuler:打造高效的AI开发环境
  • ERP和WMS系统有什么区别吗?ERP系统能代替WMS仓储管理系统吗?
  • 我在造一个编程语言,叫 Free
  • 石家庄做网站那家好做推广的公司一般都叫什么
  • 论文分享 | AlexNet:点燃深度学习革命的“一把火”
  • 拉普拉斯算子及散度
  • 前端FAQ: 如何使⽤Web Workers来提⾼⻚⾯性能?