C++学习:六个月从基础到就业——模板编程:SFINAE原则
C++学习:六个月从基础到就业——模板编程:SFINAE原则
本文是我C++学习之旅系列的第三十六篇技术文章,也是第二阶段"C++进阶特性"的第十四篇,主要介绍C++模板编程中的SFINAE原则。查看完整系列目录了解更多内容。
目录
- C++学习之旅:模板编程:SFINAE原则
- 目录
- 引言
- SFINAE基础
- 什么是SFINAE
- 模板实例化过程
- SFINAE的规则与限制
- SFINAE的实现技术
- std::enable_if
- 类型特性与标签分发
- void_t技巧
- decltype与表达式SFINAE
- 常见应用场景
- 函数重载控制
- 类型特性检测
- 约束模板参数
- 成员检测与编译期反射
- SFINAE的高级应用
- 完美转发结合SFINAE
- 条件继承和条件成员
- 编译期断言与错误消息
- SFINAE vs Concepts
- SFINAE的局限性
- Concepts的优势
- 何时使用SFINAE vs Concepts
- 实际应用案例
- 通用序列化框架
- 智能容器适配器
- 特性检测库
- 最佳实践与注意事项
- 提高代码可读性
- 优化编译时间
- 调试SFINAE代码
- 总结
- 参考资源
引言
模板是C++语言中最强大的特性之一,但与此同时也是最复杂的部分。在前面的文章中,我们已经探讨了函数模板、类模板、模板特化和可变参数模板等技术。这些技术使我们能够编写通用代码,但有时我们需要更精细地控制模板的行为,特别是当涉及到根据类型特性选择不同实现路径时。
SFINAE (Substitution Failure Is Not An Error,替换失败不是错误) 是C++模板编程中的一个关键原则,它允许编译器在特定条件下静默地忽略某些函数模板,而不报错。这一机制是许多高级模板技术的基础,如类型特性检测、条件编译和编译期反射。
虽然C++20引入的概念(Concepts)提供了更直接的方式来约束模板,但SFINAE仍然是理解现代C++库和框架的必要知识。本文将深入探讨SFINAE原则的工作机制、应用场景和实现技术,帮助你掌握这一强大的模板编程工具。
SFINAE基础
什么是SFINAE
SFINAE是"Substitution Failure Is Not An Error"(替换失败不是错误)的缩写,它是C++模板实例化过程中的一个基本原则。简单来说,当编译器尝试用具体类型替换模板参数时,如果替换导致了无效的代码(例如,使用了不存在的类型成员或无效的运算),编译器不会立即报错,而是简单地将该模板从重载解析的候选集中移除。
这一原则允许我们编写基于类型特性的条件模板代码。例如,我们可以为具有特定成员函数的类型提供一个版本的函数,为其他类型提供另一个版本。
最简单的SFINAE示例:
#include <iostream>
#include <type_traits>// 这个版本只对整数类型有效
template <typename T>
typename std::enable_if<std::is_integral<T>::value, bool>::type
is_even(T t) {std::cout << "Integer version called" << std::endl;return t % 2 == 0;
}// 这个版本对非整数类型有效
template <typename T>
typename std::enable_if<!std::is_integral<T>::value, bool>::type
is_even(T t) {std::cout << "Non-integer version called" << std::endl;return false; // 非整数没有"偶数"的概念
}int main() {is_even(42); // 调用整数版本is_even(42.0); // 调用非整数版本is_even("hello"); // 调用非整数版本return 0;
}
在这个例子中,std::enable_if
使用SFINAE来根据T
是否为整数类型选择不同的函数实现。
模板实例化过程
要理解SFINAE,首先需要了解模板实例化过程:
- 名称查找:编译器查找与函数调用匹配的名称
- 模板参数推导:根据函数调用确定模板参数的具体类型
- 替换:编译器将推导的类型替换到模板定义中
- 检查有效性:检查替换后的代码是否有效
- 重载解析:如果有多个候选函数,选择最匹配的一个
SFINAE发生在第4步。如果替换导致无效代码(即发生"替换失败"),编译器不会立即报错,而是将该模板从候选集中移除,继续考虑其他候选项。只有当所有候选项都被移除后,编译器才会报错。
这个过程可以通过一个经典例子来说明:
#include <iostream>// 第一个模板,使用T::type
template <typename T>
typename T::type test(int);// 第二个模板,回退选项
template <typename T>
char test(...);// 有内部type的类型
struct HasType { using type = int; };// 没有内部type的类型
struct NoType { };int main() {// 对于HasType,第一个模板有效,返回intstd::cout << "sizeof(test<HasType>(0)) = " << sizeof(test<HasType>(0)) << std::endl;// 对于NoType,第一个模板无效(SFINAE),使用第二个模板std::cout << "sizeof(test<NoType>(0)) = " << sizeof(test<NoType>(0)) << std::endl;return 0;
}
输出:
sizeof(test<HasType>(0)) = 4 // 假设int的大小是4字节
sizeof(test<NoType>(0)) = 1 // char的大小是1字节
在这个例子中,当T
是HasType
时,表达式typename T::type
是有效的,所以第一个模板被使用。而当T
是NoType
时,由于NoType
没有名为type
的成员,第一个模板实例化失败,但这不是错误(SFINAE),编译器转而使用第二个模板。
SFINAE的规则与限制
SFINAE只适用于特定情况下的替换失败:
-
类型替换失败
- 使用不存在的类型成员(如上例中的
T::type
) - 模板参数不满足模板约束
- 使用不存在的类型成员(如上例中的
-
表达式替换失败(C++11起)
- 使用无效的表达式在decltype、sizeof或noexcept中
但是,SFINAE不适用于以下情况:
- 语义错误:例如,尝试使用非常量表达式作为模板参数的值
- 违反访问控制:例如,尝试访问私有成员
- 硬错误:如语法错误、ODR违规等
例如,以下代码不会触发SFINAE:
// 这里的错误不会被SFINAE处理,而是直接导致编译失败
template <typename T>
void broken(T t) {t.some_method_that_might_not_exist(); // 语义错误,不是SFINAE
}
SFINAE的实现技术
std::enable_if
std::enable_if
是SFINAE最常用的工具,定义在<type_traits>
头文件中:
template <bool B, typename T = void>
struct enable_if {};template <typename T>
struct enable_if<true, T> {using type = T;
};
它的工作原理是:
- 当条件为
true
时,enable_if<true, T>::type
定义为T
- 当条件为
false
时,enable_if<false, T>::type
不存在,导致SFINAE失败
可以通过三种主要方式使用enable_if
:
- 作为返回类型:
template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type
multiply(T a, T b) {return a * b;
}
- 作为额外的模板参数:
template <typename T, typename = typename std::enable_if<std::is_arithmetic<T>::value>::type>
T multiply(T a, T b) {return a * b;
}
- 作为函数参数:
template <typename T>
auto multiply(T a, T b) -> typename std::enable_if<std::is_arithmetic<T>::value, T>::type {return a * b;
}
在C++14中,我们可以使用std::enable_if_t
简化语法:
template <typename T>
std::enable_if_t<std::is_arithmetic_v<T>, T> // C++17中的_v后缀
multiply(T a, T b) {return a * b;
}
类型特性与标签分发
另一种实现SFINAE的技术是标签分发(Tag Dispatching)。这种方法使用类型特性来创建标签类型,然后通过重载解析选择正确的实现:
#include <iostream>
#include <type_traits>
#include <vector>
#include <list>// 标签类型
struct random_access_tag {};
struct bidirectional_tag {};// 根据迭代器类型选择标签
template <typename Iterator>
auto get_iterator_tag() {if constexpr (std::is_same_v<typename std::iterator_traits<Iterator>::iterator_category, std::random_access_iterator_tag>) {return random_access_tag{};} else {return bidirectional_tag{};}
}// 对随机访问迭代器优化的版本
template <typename Iterator>
void advance_impl(Iterator& it, int n, random_access_tag) {std::cout << "Using random access version" << std::endl;it += n; // O(1) 操作
}// 对双向迭代器的一般版本
template <typename Iterator>
void advance_impl(Iterator& it, int n, bidirectional_tag) {std::cout << "Using bidirectional version" << std::endl;// O(n) 操作if (n >= 0) {for (int i = 0; i < n; ++i) ++it;} else {for (int i = 0; i > n; --i) --it;}
}// 统一接口
template <typename Iterator>
void advance(Iterator& it, int n) {advance_impl(it, n, get_iterator_tag<Iterator>());
}int main() {std::vector<int> vec = {1, 2, 3, 4, 5};auto vec_it = vec.begin();advance(vec_it, 2); // 使用随机访问版本std::list<int> list = {1, 2, 3, 4, 5};auto list_it = list.begin();advance(list_it, 2); // 使用双向版本return 0;
}
标签分发的优点是代码更加清晰,不需要复杂的模板元编程。它特别适合于基于迭代器类型或其他特性实现不同优化版本的算法。
void_t技巧
C++17引入了std::void_t
,这是一个非常强大的SFINAE工具,可用于检测任意表达式的有效性:
template <typename...>
using void_t = void;
尽管看起来很简单,但它可以用来创建复杂的类型特性:
#include <type_traits>
#include <iostream>// 在C++17之前,手动实现void_t
template <typename...>
using void_t = void;// 检查类型是否有size()方法
template <typename T, typename = void>
struct has_size_method : std::false_type {};template <typename T>
struct has_size_method<T, void_t<decltype(std::declval<T>().size())>> : std::true_type {};// 检查类型是否可打印
template <typename T, typename = void>
struct is_printable : std::false_type {};template <typename T>
struct is_printable<T, void_t<decltype(std::declval<std::ostream&>() << std::declval<T>())
>> : std::true_type {};// 使用示例
int main() {std::cout << "Vector has size(): " << has_size_method<std::vector<int>>::value << std::endl;std::cout << "int has size(): " << has_size_method<int>::value << std::endl;std::cout << "int is printable: " << is_printable<int>::value << std::endl;std::cout << "vector<int> is printable: " << is_printable<std::vector<int>>::value << std::endl;return 0;
}
void_t
的工作原理是:
- 如果表达式有效,
void_t<expr>
会返回void
- 如果表达式无效,
void_t<expr>
会导致SFINAE失败,选择另一个模板特化
decltype与表达式SFINAE
从C++11开始,decltype可以与SFINAE结合使用,用于检测表达式的有效性:
#include <iostream>
#include <type_traits>// 使用decltype和SFINAE检测+=运算符
template <typename T, typename U = T>
auto has_addition_assignment(int)-> decltype(std::declval<T&>() += std::declval<U>(), std::true_type{});template <typename, typename>
auto has_addition_assignment(...)-> std::false_type;// 一个不支持+=的类型
struct NoAddAssign {void operator++(int) {} // 支持++但不支持+=
};int main() {std::cout << "int has +=: " << decltype(has_addition_assignment<int>(0))::value << std::endl;std::cout << "NoAddAssign has +=: " << decltype(has_addition_assignment<NoAddAssign>(0))::value << std::endl;return 0;
}
使用decltype的表达式SFINAE可以检查各种复杂的表达式:
// 检查类型是否可比较
template <typename T, typename U = T>
auto is_equality_comparable(int)-> decltype(std::declval<T>() == std::declval<U>(), std::true_type{});template <typename, typename>
auto is_equality_comparable(...)-> std::false_type;// 检查类型是否可调用
template <typename F, typename... Args>
auto is_callable(int)-> decltype(std::declval<F>()(std::declval<Args>()...), std::true_type{});template <typename, typename...>
auto is_callable(...)-> std::false_type;
常见应用场景
函数重载控制
SFINAE最常见的应用是控制函数重载,根据类型特性选择最合适的实现:
#include <iostream>
#include <type_traits>
#include <vector>
#include <list>// 对有随机访问迭代器的容器使用二分查找
template <typename Container>
typename std::enable_if<std::is_same<typename std::iterator_traits<typename Container::iterator>::iterator_category,std::random_access_iterator_tag>::value,bool
>::type
contains(const Container& c, const typename Container::value_type& value) {std::cout << "Using binary search algorithm" << std::endl;auto first = c.begin();auto last = c.end();// 二分查找while (first < last) {auto mid = first + (last - first) / 2;if (*mid < value) {first = mid + 1;} else if (value < *mid) {last = mid;} else {return true; // 找到元素}}return false; // 未找到元素
}// 对其他容器使用线性查找
template <typename Container>
typename std::enable_if<!std::is_same<typename std::iterator_traits<typename Container::iterator>::iterator_category,std::random_access_iterator_tag>::value,bool
>::type
contains(const Container& c, const typename Container::value_type& value) {std::cout << "Using linear search algorithm" << std::endl;for (const auto& item : c) {if (item == value) {return true;}}return false;
}int main() {std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};std::list<int> lst = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};std::cout << "Vector contains 5: " << contains(vec, 5) << std::endl;std::cout << "List contains 5: " << contains(lst, 5) << std::endl;return 0;
}
类型特性检测
SFINAE可以用来创建自定义的类型特性,检测类型是否具有特定功能:
#include <iostream>
#include <type_traits>
#include <vector>
#include <string>// 检查是否有to_string方法
template <typename T, typename = void>
struct has_to_string : std::false_type {};template <typename T>
struct has_to_string<T, std::void_t<decltype(std::declval<T>().to_string())
>> : std::true_type {};// 有to_string方法的类型
struct HasToString {std::string to_string() const { return "HasToString object"; }
};// 没有to_string方法的类型
struct NoToString {};// 基于has_to_string使用SFINAE
template <typename T>
std::enable_if_t<has_to_string<T>::value, std::string>
convert_to_string(const T& obj) {return obj.to_string();
}template <typename T>
std::enable_if_t<!has_to_string<T>::value, std::string>
convert_to_string(const T&) {return "Object doesn't have to_string method";
}int main() {HasToString has;NoToString no;std::cout << "HasToString: " << convert_to_string(has) << std::endl;std::cout << "NoToString: " << convert_to_string(no) << std::endl;return 0;
}
约束模板参数
SFINAE可以用来约束模板参数,确保它们满足特定要求:
#include <iostream>
#include <type_traits>
#include <string>
#include <vector>// 计算数值类型的平均值
template <typename T>
std::enable_if_t<std::is_arithmetic<T>::value, double>
average(const std::vector<T>& values) {if (values.empty()) return 0.0;double sum = 0.0;for (const auto& v : values) {sum += v;}return sum / values.size();
}// 对于非数值类型,此函数不会被编译
template <typename T>
std::enable_if_t<!std::is_arithmetic<T>::value, double>
average(const std::vector<T>&) {// 编译期错误:不能对非数值类型计算平均值static_assert(std::is_arithmetic<T>::value, "Cannot compute average of non-arithmetic types");return 0.0;
}int main() {std::vector<int> ints = {1, 2, 3, 4, 5};std::vector<double> doubles = {1.5, 2.5, 3.5};std::cout << "Average of ints: " << average(ints) << std::endl;std::cout << "Average of doubles: " << average(doubles) << std::endl;// 以下代码会产生编译错误// std::vector<std::string> strings = {"a", "b", "c"};// average(strings);return 0;
}
成员检测与编译期反射
SFINAE使我们能够实现基本的编译期反射,检测类型是否具有特定的成员:
#include <iostream>
#include <type_traits>
#include <string>
#include <vector>
#include <map>// 通用的成员检测宏
#define GENERATE_HAS_MEMBER(member) \
template <typename T, typename = void> \
struct has_member_##member : std::false_type {}; \
template <typename T> \
struct has_member_##member<T, std::void_t<decltype(&T::member)>> : std::true_type {}// 为特定成员生成检测器
GENERATE_HAS_MEMBER(name)
GENERATE_HAS_MEMBER(size)
GENERATE_HAS_MEMBER(data)// 测试类型
struct CompleteType {std::string name;size_t size() const { return 0; }int data[10];
};struct PartialType {std::string name;
};int main() {std::cout << "CompleteType has name: " << has_member_name<CompleteType>::value << std::endl;std::cout << "CompleteType has size: " << has_member_size<CompleteType>::value << std::endl;std::cout << "CompleteType has data: " << has_member_data<CompleteType>::value << std::endl;std::cout << "PartialType has name: " << has_member_name<PartialType>::value << std::endl;std::cout << "PartialType has size: " << has_member_size<PartialType>::value << std::endl;std::cout << "PartialType has data: " << has_member_data<PartialType>::value << std::endl;return 0;
}
这种技术使我们能够在编译期检测类型的结构,实现类似反射的功能。
SFINAE的高级应用
完美转发结合SFINAE
结合完美转发和SFINAE,我们可以创建更通用的函数模板:
#include <iostream>
#include <type_traits>
#include <utility>
#include <string>// 仅针对有push_back方法的容器
template <typename Container, typename T>
auto add_element(Container& c, T&& value)-> decltype(c.push_back(std::forward<T>(value)), void()) {std::cout << "Using push_back version" << std::endl;c.push_back(std::forward<T>(value));
}// 仅针对有insert方法的容器
template <typename Container, typename T>
auto add_element(Container& c, T&& value)-> decltype(c.insert(c.end(), std::forward<T>(value)), void()) {std::cout << "Using insert version" << std::endl;c.insert(c.end(), std::forward<T>(value));
}// 测试容器
#include <vector>
#include <set>int main() {std::vector<int> vec;std::set<int> set;add_element(vec, 42); // 使用push_back版本add_element(set, 42); // 使用insert版本return 0;
}
这个例子展示了如何根据容器支持的操作选择不同的实现,并使用完美转发保留值类别。
条件继承和条件成员
SFINAE允许我们实现条件继承和条件成员,根据类型特性定制类的行为:
#include <iostream>
#include <type_traits>
#include <string>
#include <vector>
#include <map>// 基类提供默认实现
template <typename Key, typename Value>
struct DefaultOperations {void print() const {std::cout << "Default print operation" << std::endl;}
};// 条件继承示例
template <typename Container, typename Enable = void>
class DataStore : public DefaultOperations<typename Container::key_type,typename Container::mapped_type
> {
private:Container data;public:void add(const typename Container::key_type& key, const typename Container::mapped_type& value) {data[key] = value;}
};// 为vector类型的特化,不继承DefaultOperations
template <typename T>
class DataStore<std::vector<T>,void
> {
private:std::vector<T> data;public:void add(const T& value) {data.push_back(value);}// 特化的print方法void print() const {std::cout << "Vector specialization with " << data.size() << " elements" << std::endl;}
};// 条件成员示例
template <typename T>
class TypeInfo {
private:// 对于可哈希类型添加哈希方法template <typename U = T>typename std::enable_if<std::is_integral<U>::value, size_t>::typecompute_hash_impl(const U& value) const {return static_cast<size_t>(value);}// 对于其他类型,提供空实现template <typename U = T>typename std::enable_if<!std::is_integral<U>::value, size_t>::typecompute_hash_impl(const U&) const {return 0;}public:// 公共接口,委托给适当的实现size_t compute_hash(const T& value) const {return compute_hash_impl(value);}// 另一种条件成员的方法:使用deleted函数template <typename U = T>typename std::enable_if<std::is_floating_point<U>::value>::typespecial_process(U value) {std::cout << "Processing floating point: " << value << std::endl;}template <typename U = T>typename std::enable_if<!std::is_floating_point<U>::value>::typespecial_process(U) = delete; // 对非浮点类型禁用此函数
};int main() {DataStore<std::map<std::string, int>> map_store;map_store.add("one", 1);map_store.print(); // 使用默认操作DataStore<std::vector<double>> vec_store;vec_store.add(3.14);vec_store.print(); // 使用特化的print方法TypeInfo<int> int_info;std::cout << "Hash of 42: " << int_info.compute_hash(42) << std::endl;TypeInfo<double> double_info;double_info.special_process(3.14); // 可以调用TypeInfo<std::string> string_info;// string_info.special_process("hello"); // 编译错误,函数已删除return 0;
}
编译期断言与错误消息
SFINAE可以与static_assert
结合,提供更友好的编译期错误消息:
#include <iostream>
#include <type_traits>
#include <string>
#include <vector>// 安全的数组访问函数
template <typename Container>
auto safe_access(const Container& container, size_t index)-> typename std::enable_if<std::is_same<typename std::iterator_traits<typename Container::iterator>::iterator_category,std::random_access_iterator_tag>::value,typename Container::const_reference>::type {if (index < container.size()) {return container[index];}throw std::out_of_range("Index out of bounds");
}// 对于非随机访问容器,提供更好的编译错误
template <typename Container>
auto safe_access(const Container&, size_t)-> typename std::enable_if<!std::is_same<typename std::iterator_traits<typename Container::iterator>::iterator_category,std::random_access_iterator_tag>::value,typename Container::const_reference>::type {static_assert(std::is_same<typename std::iterator_traits<typename Container::iterator>::iterator_category,std::random_access_iterator_tag>::value,"safe_access requires a random access container");// 这里的代码永远不会被执行,因为static_assert会在编译期触发错误throw std::logic_error("This code should never execute");
}int main() {std::vector<int> vec = {1, 2, 3, 4, 5};std::cout << "Vector element 2: " << safe_access(vec, 2) << std::endl;// 以下代码会产生友好的编译错误// std::list<int> lst = {1, 2, 3, 4, 5};// std::cout << "List element 2: " << safe_access(lst, 2) << std::endl;return 0;
}
这种技术允许我们在编译期提供更具描述性的错误消息,而不是复杂难懂的模板实例化错误。
SFINAE vs Concepts
SFINAE的局限性
虽然SFINAE强大,但它也有一些局限性:
- 错误消息复杂:当SFINAE失败时,编译器产生的错误消息通常很难理解
- 代码冗长:使用SFINAE的代码通常很冗长,难以编写和维护
- 调试困难:SFINAE错误可能发生在深层的模板实例化中,难以定位问题
- 性能开销:复杂的SFINAE表达式可能增加编译时间
Concepts的优势
C++20引入的Concepts旨在解决SFINAE的许多问题:
- 更清晰的语法:Concepts提供了更直观的语法来表达约束
- 更好的错误消息:当约束不满足时,编译器提供更有用的错误消息
- 可重用的约束:Concepts可以被命名和重用
- 支持逻辑组合:Concepts可以通过逻辑运算符组合
SFINAE与Concepts的对比:
// 使用SFINAE
template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
increment(T value) {return value + 1;
}// 使用Concepts (C++20)
template <typename T>
requires std::integral<T>
T increment(T value) {return value + 1;
}// 或者使用简写语法
template <std::integral T>
T increment(T value) {return value + 1;
}
何时使用SFINAE vs Concepts
在选择使用SFINAE还是Concepts时,考虑以下因素:
- 语言版本:如果需要支持C++17或更早版本,必须使用SFINAE
- 复杂性:对于复杂的约束,Concepts更加清晰
- 可维护性:Concepts更易于维护和理解
- 编译器支持:确保目标编译器支持Concepts
在C++20之前的代码中,SFINAE仍然是实现模板约束的主要方式。随着C++20的广泛采用,新代码应优先考虑使用Concepts。
实际应用案例
通用序列化框架
SFINAE可用于构建通用序列化框架,根据类型特性选择合适的序列化方法:
#include <iostream>
#include <type_traits>
#include <string>
#include <vector>
#include <map>
#include <sstream>// 序列化的基本接口
class Serializer {
public:// 序列化基本类型template <typename T>typename std::enable_if<std::is_arithmetic<T>::value, std::string>::typeserialize(const T& value) {return std::to_string(value);}// 序列化字符串std::string serialize(const std::string& value) {return "\"" + value + "\"";}// 序列化带有to_string方法的自定义类型template <typename T>typename std::enable_if<!std::is_arithmetic<T>::value && !std::is_same<T, std::string>::value,std::string>::typeserialize(const T& value) {return serialize_object(value, 0);}private:// 检查类型是否有serialize_to方法template <typename T>auto serialize_object(const T& obj, int)-> decltype(obj.serialize_to(std::declval<Serializer&>()), std::string()) {std::stringstream ss;ss << "{custom}";obj.serialize_to(*this);return ss.str();}// 对于没有serialize_to方法的类型,尝试使用to_stringtemplate <typename T>auto serialize_object(const T& obj, long)-> decltype(obj.to_string(), std::string()) {return obj.to_string();}// 最后的回退,不能序列化的类型template <typename T>std::string serialize_object(const T&, ...) {return "{not-serializable}";}
};// 测试类型
class CustomSerializable {
private:int id;std::string name;public:CustomSerializable(int i, std::string n) : id(i), name(n) {}void serialize_to(Serializer& s) const {std::cout << "Custom serialization: id=" << s.serialize(id) << ", name=" << s.serialize(name) << std::endl;}
};class StringConvertible {
private:double value;public:explicit StringConvertible(double v) : value(v) {}std::string to_string() const {return "StringConvertible(" + std::to_string(value) + ")";}
};class NonSerializable {// 没有序列化方法
};int main() {Serializer serializer;// 基本类型std::cout << "Int: " << serializer.serialize(42) << std::endl;std::cout << "Double: " << serializer.serialize(3.14) << std::endl;std::cout << "String: " << serializer.serialize("Hello, world!") << std::endl;// 自定义类型CustomSerializable custom(1, "example");std::cout << "CustomSerializable: " << serializer.serialize(custom) << std::endl;StringConvertible convertible(2.71);std::cout << "StringConvertible: " << serializer.serialize(convertible) << std::endl;NonSerializable non_serializable;std::cout << "NonSerializable: " << serializer.serialize(non_serializable) << std::endl;return 0;
}
这个例子演示了如何使用SFINAE构建一个通用序列化框架,它可以根据类型的特性选择合适的序列化方法。
智能容器适配器
使用SFINAE可以创建智能容器适配器,根据容器类型提供优化的操作:
#include <iostream>
#include <type_traits>
#include <vector>
#include <list>
#include <set>
#include <algorithm>// 容器适配器
template <typename Container>
class SmartContainer {
private:Container container;public:// 构造函数SmartContainer() = default;// 初始化列表构造template <typename... Args>SmartContainer(Args&&... args) : container{std::forward<Args>(args)...} {}// 添加元素template <typename T>void add(T&& value) {add_impl(std::forward<T>(value), 0);}// 查找元素template <typename T>bool contains(const T& value) const {return contains_impl(value, 0);}// 获取底层容器const Container& get_container() const { return container; }private:// 添加元素的不同实现// 对于有push_back方法的容器template <typename T>auto add_impl(T&& value, int)-> decltype(container.push_back(std::declval<T>()), void()) {std::cout << "Using push_back" << std::endl;container.push_back(std::forward<T>(value));}// 对于set类型容器template <typename T>auto add_impl(T&& value, long)-> decltype(container.insert(std::declval<T>()), void()) {std::cout << "Using insert" << std::endl;container.insert(std::forward<T>(value));}// 查找元素的不同实现// 对于有find方法的关联容器template <typename T>auto contains_impl(const T& value, int) const-> decltype(container.find(value) != container.end(), bool()) {std::cout << "Using associative container find" << std::endl;return container.find(value) != container.end();}// 对于序列容器,使用std::findtemplate <typename T>auto contains_impl(const T& value, long) const-> decltype(std::find(container.begin(), container.end(), value), bool()) {std::cout << "Using std::find" << std::endl;return std::find(container.begin(), container.end(), value) != container.end();}// 最后的回退template <typename T>bool contains_impl(const T&, ...) const {std::cout << "Container doesn't support finding elements" << std::endl;return false;}
};int main() {// 使用vectorSmartContainer<std::vector<int>> vec_container;vec_container.add(1);vec_container.add(2);vec_container.add(3);std::cout << "Vector contains 2: " << vec_container.contains(2) << std::endl;std::cout << "Vector contains 4: " << vec_container.contains(4) << std::endl;// 使用setSmartContainer<std::set<int>> set_container;set_container.add(10);set_container.add(20);set_container.add(30);std::cout << "Set contains 20: " << set_container.contains(20) << std::endl;std::cout << "Set contains 40: " << set_container.contains(40) << std::endl;return 0;
}
这个智能容器适配器可以根据底层容器的特性选择最优的实现方式。
特性检测库
SFINAE可以用于构建通用的特性检测库,用于在编译时检测类型的各种特性:
#include <iostream>
#include <type_traits>
#include <string>
#include <vector>// 特性检测的基础工具
namespace traits {// void_t实现template <typename...>using void_t = void;// 检测是否有特定成员变量#define GENERATE_HAS_MEMBER_VAR(var) \template <typename T, typename = void> \struct has_member_##var : std::false_type {}; \template <typename T> \struct has_member_##var<T, void_t<decltype(std::declval<T>().var)>> : std::true_type {};// 检测是否有特定成员函数#define GENERATE_HAS_MEMBER_FUNC(func) \template <typename T, typename = void> \struct has_member_func_##func : std::false_type {}; \template <typename T> \struct has_member_func_##func<T, void_t<decltype(std::declval<T>().func())>> : std::true_type {};// 检测是否有特定类型成员#define GENERATE_HAS_TYPE(type) \template <typename T, typename = void> \struct has_type_##type : std::false_type {}; \template <typename T> \struct has_type_##type<T, void_t<typename T::type>> : std::true_type {};// 检测是否可以用特定操作符template <typename T, typename U = T, typename = void>struct is_equality_comparable : std::false_type {};template <typename T, typename U>struct is_equality_comparable<T, U, void_t<decltype(std::declval<T>() == std::declval<U>())>> : std::true_type {};template <typename T, typename U = T, typename = void>struct is_less_than_comparable : std::false_type {};template <typename T, typename U>struct is_less_than_comparable<T, U, void_t<decltype(std::declval<T>() < std::declval<U>())>> : std::true_type {};
}// 生成一些特性检测器
GENERATE_HAS_MEMBER_VAR(size)
GENERATE_HAS_MEMBER_FUNC(clear)
GENERATE_HAS_TYPE(iterator)// 测试类型
struct CompleteType {size_t size;void clear() {}using iterator = int*;
};struct PartialType {size_t size;
};int main() {// 测试成员变量检测std::cout << "CompleteType has size: " << traits::has_member_size<CompleteType>::value << std::endl;std::cout << "PartialType has size: " << traits::has_member_size<PartialType>::value << std::endl;std::cout << "int has size: " << traits::has_member_size<int>::value << std::endl;// 测试成员函数检测std::cout << "CompleteType has clear(): " << traits::has_member_func_clear<CompleteType>::value << std::endl;std::cout << "PartialType has clear(): " << traits::has_member_func_clear<PartialType>::value << std::endl;std::cout << "vector<int> has clear(): " << traits::has_member_func_clear<std::vector<int>>::value << std::endl;// 测试类型成员检测std::cout << "CompleteType has iterator type: " << traits::has_type_iterator<CompleteType>::value << std::endl;std::cout << "vector<int> has iterator type: " << traits::has_type_iterator<std::vector<int>>::value << std::endl;// 测试操作符检测std::cout << "int is equality comparable: " << traits::is_equality_comparable<int>::value << std::endl;std::cout << "int is less-than comparable: " << traits::is_less_than_comparable<int>::value << std::endl;struct NoCompare {};std::cout << "NoCompare is equality comparable: " << traits::is_equality_comparable<NoCompare>::value << std::endl;return 0;
}
这个特性检测库可以用于编写更通用、更灵活的代码,根据类型的特性自动调整行为。
最佳实践与注意事项
提高代码可读性
SFINAE代码往往复杂难读,这里有一些提高可读性的技巧:
- 使用类型别名:将复杂的SFINAE表达式封装为类型别名
- 隐藏实现细节:将SFINAE技术封装在私有实现中,提供简单的公共接口
- 添加注释:解释SFINAE代码的目的和工作原理
- 使用辅助模板:创建辅助模板降低复杂度
示例改进:
// 改进前
template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value && !std::is_same<T, bool>::value,T
>::type calculate(T value) {return value * 2;
}// 改进后
// 1. 使用类型别名
template <typename T>
using EnableIfNumeric = typename std::enable_if<std::is_arithmetic<T>::value && !std::is_same<T, bool>::value,T
>::type;template <typename T>
EnableIfNumeric<T> calculate(T value) {return value * 2;
}// 2. 使用辅助模板
template <typename T>
struct is_numeric : std::integral_constant<bool, std::is_arithmetic<T>::value && !std::is_same<T, bool>::value> {};template <typename T>
typename std::enable_if<is_numeric<T>::value, T>::type
calculate(T value) {return value * 2;
}
优化编译时间
复杂的SFINAE表达式会增加编译时间,这里有一些优化方法:
- 减少嵌套的模板实例化:过多的嵌套会导致编译时间指数增长
- 在较浅层次应用SFINAE:尽早过滤不适用的模板
- 使用预编译头文件:将常用的SFINAE工具放在预编译头文件中
- 限制SFINAE的应用范围:只在必要时使用SFINAE
调试SFINAE代码
调试SFINAE代码可能很困难,这里有一些技巧:
- 使用static_assert:添加静态断言验证模板参数
- 分步构建:逐步构建复杂的SFINAE表达式,确保每一步都正常工作
- 打印类型信息:使用
typeid
或自定义工具打印类型信息
template <typename T>
void debug_type() {std::cout << "Type name: " << typeid(T).name() << std::endl;std::cout << " is_integral: " << std::is_integral<T>::value << std::endl;std::cout << " is_floating_point: " << std::is_floating_point<T>::value << std::endl;std::cout << " is_class: " << std::is_class<T>::value << std::endl;
}template <typename T>
auto complex_sfinae_function(T value)-> decltype(/* 复杂的SFINAE表达式 */) {// 添加类型调试debug_type<T>();// 添加静态断言static_assert(/* 条件 */, "Detailed error message");// 函数实现
}
总结
SFINAE是C++模板编程中的一个强大原则,允许我们根据类型特性选择不同的实现路径。它是许多高级模板技术的基础,包括类型特性检测、条件编译和编译期反射。
通过本文,我们学习了:
- SFINAE的基本原理和工作机制
- 使用std::enable_if、void_t和decltype实现SFINAE
- SFINAE的常见应用场景,如函数重载控制和类型特性检测
- SFINAE的高级应用,包括完美转发和条件成员
- SFINAE与C++20 Concepts的比较
- 实际应用案例,如通用序列化框架和特性检测库
- 使用SFINAE的最佳实践和注意事项
虽然C++20的Concepts提供了更清晰、更易维护的模板约束方式,但SFINAE在现有代码库中仍然广泛存在,并且在需要支持C++17及更早版本的项目中仍然很重要。掌握SFINAE原则不仅有助于理解现代C++库的设计,也能让我们编写更灵活、更强大的泛型代码。
在下一篇文章中,我们将探讨模板元编程的基础,这是另一种强大的编译期计算技术,与SFINAE密切相关。
参考资源
- cppreference: SFINAE
- cppreference: std::enable_if
- cppreference: std::void_t
- 《C++ Templates: The Complete Guide, 2nd Edition》by David Vandevoorde, Nicolai M. Josuttis, and Douglas Gregor
- 《Modern C++ Design: Generic Programming and Design Patterns Applied》by Andrei Alexandrescu
- Walter E. Brown’s CppCon 2014 talk: “Modern Template Metaprogramming: A Compendium”
这是我C++学习之旅系列的第三十六篇技术文章。查看完整系列目录了解更多内容。
如有任何问题或建议,欢迎在评论区留言交流!
相关文章:
C++学习:六个月从基础到就业——模板编程:SFINAE原则
C学习:六个月从基础到就业——模板编程:SFINAE原则 本文是我C学习之旅系列的第三十六篇技术文章,也是第二阶段"C进阶特性"的第十四篇,主要介绍C模板编程中的SFINAE原则。查看完整系列目录了解更多内容。 目录 C学习之…...
完美解决.NET Framework 4.0 中 System.Drawing 库不支持 WebP 格式的图像处理
如果你想在 .NET Framework 4.0 中使用 ImageMagick 处理图片,可以通过 Magick.NET 库来实现。Magick.NET 是 ImageMagick 的 .NET 封装,可以用来读取、写入、编辑图像。 以下是如何使用 Magick.NET 来处理图像并提取图像的宽度和高度。 步骤ÿ…...
网络基础概念:从菜鸟到入门
前言:快递小哥的故事 想象一下你要给朋友寄个礼物,这个过程其实和网络通信非常相似: 1. 你需要知道朋友的”地址“(IP地址) 2. 要注明是送到他家大门还是物业代收(端口号) 3. 要选择快递公司并…...
优先队列和单调队列(双端队列实现的)
这里写自定义目录标题 一、优先队列与单调队列二、优先队列2.1 概念2.2 增删查 判空2.3 示例代码 三、双端队列四、单调队列4.1 单调递增队列4.2 单调递减队列 一、优先队列与单调队列 二、优先队列 2.1 概念 一种特殊的队列,它与普通队列的主要区别在于元素的出…...
设计模式(状态模式)
概述 在实际的软件开发中,状态模式并不是很常用,但是在能够用到的场景里,它可以发挥很大的作用。从这一点上来看,它有点像我们之前讲到的组合模式。 状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统…...
安卓基础(get和set)
在 Java 中,get 和 set 方法是面向对象编程中 封装(Encapsulation) 的核心实现,用于安全地访问和修改类的私有字段(private 成员变量)。它们的核心作用是 控制对数据的访问,…...
机器人灵巧操作新突破,力感知技术让机械手更精准
在机器人的发展历程中,让机器人实现灵活操作一直是科研人员努力攻克的难题。 我们这篇文章给大家带来一份新的工作:DexForce 链接:[2501.10356] DexForce: Extracting Force-informed Actions from Kinesthetic Demonstrations for Dextero…...
八大排序——直接插入排序/希尔排序
八大排序——直接插入排序/希尔排序 目录 一、直接插入排序 二、希尔排序 一、直接插入排序 每一趟从待排序序列中取第一个值,将其插入到已排序好的序列中,对已排序好的序列,从右到左依次和待插入值比较,如果大于则向后挪&…...
8、HTTPD服务--ab压力测试
一、ab压力测试 # ab ‐c 100 ‐n 1000 http://vedio.linux.com/index.html 2 This is ApacheBench, Version 2.3 <$Revision: 1430300 $> 3 Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ 4 Licensed to The Apache Software Foundation,…...
node.js 实战——mongoDB
MongoDB MongoDB 简介 MongoDB 是一种基于文档型 (document-oriented) 的 NoSQL 数据库,使用类 JSON 的 BSON 格式存储数据,自然支持复杂数据结构。它特别适合需要快速变化、大量数据处理和高应用扩展性的场景。 MongoDB 特性: 无法表、无…...
C语言学习路线
以下是一份综合多个优质资源的C语言学习路线规划,结合2025年最新技术趋势和工程实践需求,分为三个阶段系统推进: 一、入门阶段(1-2个月) 目标:掌握基础语法,能编写简单程序ÿ…...
飞凌嵌入式T527核心板获得【OpenHarmony生态产品兼容性证书】
近日,飞凌嵌入式FET527-C核心板通过OpenHarmony 4.1 Release版本兼容测评,获得【OpenHarmony生态产品兼容性证书】。 飞凌嵌入式FET527-C核心板搭载全志T527系列全国产高性能处理器,集成8个ARM Cortex-A55核心,并内置RISC-V核和DS…...
Mioty|采用报文分割(Telegram Splitting)以提高抗干扰能力的无线通信技术
1、什么是Mioty 在物联网(IoT)快速发展的背景下,低功耗广域网(LPWAN)技术成为连接海量设备的关键。LPWAN具有低功耗、低成本、广覆盖和强抗干扰能力等特点,使其特别适用于大规模、远距离、低数据速率的IoT…...
WPF 程序监控硬件设备状态变化的实现方案
以下是一个完整的 C# WPF 程序实现方案,用于监控硬件设备状态变化(基于设备 SDK API)。我们将分步骤实现,包含状态轮询、事件通知、UI 绑定和错误处理。 1. 项目结构设计 HardwareMonitor/ ├── Models/ # 数据模…...
利用Python打印有符号十进制数的二进制原码、反码、补码
有时候手动计算有符号十进制数的二进制原码、反码、补码是一件挺麻烦的事情。 下面是一个段Python 代码,它可以接收一个 16 位有符号十进制数的输入,然后输出其对应的二进制原码、反码和补码: def decimal_to_binary(decimal_num):# 检查输入…...
STM32裸机编程架构与思路
STM32作为广泛应用的微控制器系列,其强大的功能和灵活的编程方式使其成为嵌入式系统开发的优选。裸机编程(bare-metal programming)指的是在没有操作系统支持的情况下,直接对硬件进行编程。这种方式虽然较为底层,但能够…...
Eureka 深度解析:从原理到部署的全场景实践
一、Eureka 核心原理与架构设计 1. 核心定位与组件模型 Eureka 是 Netflix 开源的服务发现(Service Discovery)组件,作为 Spring Cloud 微服务体系的核心基础设施,其核心目标是解决分布式系统中服务实例动态管理与跨服务通信解耦…...
有哪些和PPT自动生成有关的MCP项目?
随着AI技术的快速发展, Model Context Protocol(MCP) 作为一种连接大型语言模型(LLMs)与外部工具的开放协议,正在重塑自动化办公领域。在PPT自动生成场景中,MCP通过标准化接口实现了AI模型与设计工具、数据源的无缝整合。以下从技术框架、项目案例、应用场景三个维度展开…...
经典数仓架构深度解析与演进:从离线处理到新型架构对比
经典数仓架构深度解析与演进:从离线处理到新型架构对比 在数据驱动决策的时代,经典数仓作为企业数据管理与分析的核心基础设施,承载着从数据存储到价值挖掘的重要使命。本文将深入剖析经典数仓的架构、数据处理流程、主流架构模式及其对比&a…...
[Python开发] 如何用 VSCode 编写和管理 Python 项目(从 PyCharm 转向)
在 Python 开发领域,PyCharm 一直是广受欢迎的 IDE,但其远程开发功能(如远程 SSH 调试)仅在付费版中提供。为了适应服务器部署需求,很多开发者开始将目光转向更加轻量、灵活且免费扩展能力强的 VSCode。本篇文章将详细介绍,从 PyCharm 转向 VSCode 后,如何高效搭建和管理…...
系统架构-架构评估
质量属性 性能 指系统的响应能力 指标:响应时间、吞吐量等。 设计策略:优先级队列、增加计算资源、减少计算开销、引入并发机制、采用资源调度 可靠性 在意外或错误使用的情况下维持软件系统的功能特性 指标:MTTF、MTBF、MTTR 设计策…...
使用 MQTT - C 访问 IoTDA 平台:一个完整的嵌入式示例
引言 在物联网(IoT)开发领域,设备与平台之间的通信至关重要。MQTT 作为一种轻量级的消息传输协议,因其高效、可靠的特性,在物联网场景中得到了广泛应用。华为的 IoTDA(IoT Device Access)平台为…...
Leetcode594.最长和谐子序列
目录 题目算法标签: 滑动窗口, 哈希表思路滑动窗口代码哈希表代码 题目 594. 最长和谐子序列 算法标签: 滑动窗口, 哈希表 思路 先将数组进行排序, 检查两个相邻的但是不相等的数字的差值是否是 1 1 1, 如果是 1 1 1更新答案 滑动窗口代码 #include <algorithm> #i…...
如何在idea中编写spark程序
在 IntelliJ IDEA 中编写 Spark 程序的详细指南 在大数据处理领域,Apache Spark 凭借其强大的分布式计算能力,成为了众多开发者的首选工具。而 IntelliJ IDEA 作为一款功能强大的集成开发环境(IDE),为编写 Spark 程序…...
人工智能数学基础(一):人工智能与数学
在人工智能领域,数学是不可或缺的基石。无论是算法的设计、模型的训练还是结果的评估,都离不开数学的支持。接下来,我将带大家深入了解人工智能数学基础,包括微积分、线性代数、概率论、数理统计和最优化理论,并通过 P…...
Android Studio 安装 Continue插件
1、Android 插件Studio中安装Continue 2、从本地盘符安装 3、安装后发现Continue为空 Android studio中 Help -> Find Action->Choose Boot Java 设置 4、配置DeepSeek 参考https://juejin.cn/post/7464122534546407461...
【C++】类和对象(4)
目录 1. 类型转换 非explicit的单参数构造函数 示例 explicit的单参数构造函数 示例 不同版本的行为 示例 (单参数) 示例(多参数且其余参数有默认值 ) 示例(多参数且无默认值) 2. static成员变量…...
微信jdk 前端vue获取流程1、
参考链接: 企业微信的JSSDK,调用及使用方法_企业微信jssdk-CSDN博客 1、引用 <script src"//res.wx.qq.com/open/js/jweixin-1.2.0.js"></script> <script src"https://res.wx.qq.com/open/js/jweixin-1.2.0.js" referrerpolic…...
Linux进程7-signal信号处理方式验证、可重入函数举例、信号集函数验证、信号集阻塞验证
目录 1. signal函数 1.1进程接收到信号后的处理方式 1.2 signal 函数 1.2.1 signal 函数默认处理 1.2.2 signal 函数忽略处理 1.2.3 signal 函数自定义处理 1.2.4 signal 函数返回值 2.可重入函数 2.1如何判断函数是否可重入 2.2自定义信号处理函数举例 2.2.1 sle…...
使用Python在excel里创建柱状图
一、前言 通过使用Python的openpyxl库,在excel里创建柱状图。openpyxl库提供了创建Excel图表的功能,包括柱状图(Bar Chart)。 二、程序展示 1、导入相关模块,新建excel 新建excel后,在excel的第一列创建一些数据。 import op…...
计算机视觉进化论:YOLOv12、YOLOv11与Darknet系YOLOv7的微调实战对比
摘要 YOLO系列作为实时目标检测领域的重要里程碑,持续引领速度与精度的平衡发展。本文围绕YOLOv7(基于Darknet框架)、YOLOv11及YOLOv12,系统、深入地对比了三款模型的架构创新、微调策略、核心技术及应用场景。我们详细解析了三者…...
湖北理元理律师事务所:债务管理领域的平台化创新探索
随着中国居民负债率攀升至62%(央行2023年数据),债务管理从个体需求演变为社会性课题。湖北理元理律师事务所通过“法律科技金融”的融合模式,构建了国内首个全链条债务管理平台,其服务逻辑与行业价值值得深度剖析。 平…...
沐曦玩转 LMDeploy、XTuner 和 InternLM3
学习链接: https://aicarrier.feishu.cn/wiki/O84LwkiBriUU0NkDwurcSufhnVb 一 LMDeploy推理及验证 1.1 下载LMDeploy # 安装addict软件包 pip install addict mmengine mmengine-lite fire accelerate0.32.1 nvidia-ml-py# 解决LMDeploy对tranformers版本要求的…...
【Java面试笔记:进阶】26.如何监控和诊断JVM堆内和堆外内存使用?
监控和诊断JVM内存使用是优化性能和解决内存问题的关键。 1.JVM内存监控与诊断方法 1.图形化工具 JConsole:提供图形化界面,可直接连接到Java进程,查看内存使用情况。VisualVM:功能强大的图形化工具,但注意从Oracle JDK 9开始不再包含在JDK安装包中。Java Mission Contr…...
阿里云服务器云盘扩容
在阿里云服务器上在线扩容了云盘后,如果服务器内部查看容量没有变化,可能是由于分区和文件系统未正确扩展。以下是详细的解决步骤: 1. 确认扩容是否成功 在阿里云控制台检查磁盘容量是否已显示扩容后的新大小。如果控制台显示已扩容&#x…...
【ESP32】st7735s + LVGL移植
LVGL的移植 使用版本1、创建工程2、开始移植2.1、文件准备2.2、修改代码2.3、SDK配置编辑器 3、测试 使用版本 LVGL版本:8.3 链接点这里ESPIDF版本:4.4.8lvgl_esp32_drivers: 链接点这里ESP32型号:ESP32S3 1、创建工程 默认都会…...
Jackson 使用方法详解
Jackson 是 Java 生态中最流行的 JSON 处理库,也是 Spring Boot 的默认 JSON 解析器。它提供了高性能的 JSON 序列化(对象 → JSON)和反序列化(JSON → 对象)功能。以下是 Jackson 的全面使用指南。 1. 基础依赖 Mave…...
TensorFlow深度学习框架:从入门到精通的完整指南
🌟 TensorFlow核心优势 TensorFlow作为Google开发的顶级深度学习框架,具有三大独特优势: 工业级部署能力:支持从移动端到服务器的全平台部署完善的工具链:包含TensorBoard、TF Lite、TF.js等完整生态强大的社区支持&…...
Java 入门宝典--注释、关键字、数据类型、变量常量、类型转换
作者:IvanCodes 发布时间:2025年4月28日🐣 专栏:Java教程 哈喽,各位 CSDN 的小伙伴们!👋 这部分内容虽然基础,但 极其重要,是后续学习所有高级特性的基石。准备好了吗&…...
【含文档+PPT+源码】基于微信小程序的旅游论坛系统的设计与实现
项目介绍 本课程演示的是一款基于微信小程序的旅游论坛系统的设计与实现,主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。 1.包含:项目源码、项目文档、数据库脚本、软件工具等所有资料 2.带你从零开始部署运行本套系统 …...
Android开发,实现一个简约又好看的登录页
文章目录 1. 编写布局文件2.设计要点说明3. 效果图4. 关于作者其它项目视频教程介绍 1. 编写布局文件 编写activity.login.xml 布局文件 <?xml version"1.0" encoding"utf-8"?> <androidx.appcompat.widget.LinearLayoutCompat xmlns:android…...
一种改进的YOLOv11网络,用于无人机视角下的小目标检测
大家读完觉得有帮助记得关注和点赞!!! 摘要 随着无人机(UAV)和计算机视觉技术的快速发展,从无人机视角进行目标检测已成为一个重要的研究领域。然而,无人机图像中目标像素占比极小、物体尺度变…...
linux离线安装zsh
下载zsh 下载仓库后解压 下载地址:https://github.com/zsh-users/zsh 离线安装 安装方法见INSTALL文件 ./configure --prefix[/usr/local] make make install...
Golang|使用函数作为参数和使用接口的联系
函数作为数据类型的一种,可以成为其他函数的参数。在 Go(Golang) 中,函数作为参数 和 接口(interface),本质上都和抽象、灵活调用有关 —— 都是让代码更灵活、更可扩展的手段。不过它们各有侧重…...
Python爬虫实战:获取软科网最新特定专业大学排名数据并做分析,为高考填报志愿做参考
一、引言 在高考升学的重要阶段,志愿填报成为考生和家长关注的核心问题。准确、全面且具有权威性的大学专业排名数据,是考生做出科学志愿决策的关键依据。软科网作为专业的大学排名信息发布平台,其发布的计算机科学与技术专业排名数据,因具有较高的公信力和参考价值,备受…...
【ACL系列论文写作指北12-Deadline管理与科研项目规划】-用节奏赢得高质量科研
科研不是一场冲刺,而是有序推进的系统工程。 引言:掌控时间,才能掌控科研主动权 再好的想法和技术,如果没有良好的时间管理,最终只会沦为“赶DDL”的牺牲品。科研项目规划,是确保质量、效率与心态平衡的关…...
elasticsearch底层模块解析与实践系列
#作者:猎人 文章目录 底层模块深入解析之threadpool1、线程池2、线程池类型3、cpu core数量设置 底层模块深入解析之plugin底层模块深入解析之es node节点角色1、node类型2、master eligible node3、data node4、ingest node5、cooridnating only node6、node data…...
Git-基本操作
前言 安装 git --version sudo apt-get remove git -y #卸载 sudo apt-get install git -y基本操作 创建本地仓库 mkdir gitcodegit init 这个就可以创建本地仓库了 然后当前目录下就有一个.git的文件夹 配置本地仓库 就是配置用户的名称,和用户的email地址 在…...
iVX 图形化编程如何改写后端开发新范式
在数字化转型加速推进的当下,企业对后端系统的需求呈现爆发式增长。Gartner 最新报告指出,2025 年全球企业平均需完成 300 定制化应用开发,而传统编码模式下,单个项目平均交付周期长达 6 - 8 个月。与此同时,Redis、K…...
【数据可视化-42】杂货库存数据集可视化分析
🧑 博主简介:曾任某智慧城市类企业算法总监,目前在美国市场的物流公司从事高级算法工程师一职,深耕人工智能领域,精通python数据挖掘、可视化、机器学习等,发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…...