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

第十九章:实现 Traits_《C++ Templates》notes

实现 Traits

      • 1. Traits 概念
      • 2. 示例:累加序列(Accumulating a Sequence)
      • 3. 固定特性(Fixed Traits)
      • 4. 值特性(Value Traits)
      • 5. 参数化特性(Parameterized Traits)
      • 6. SFINAE-based Traits
      • 多选题(附答案与详解)
      • 设计题(附答案与详解)
      • 总结

1. Traits 概念

Traits(特性类)是用于在编译期提取或推断类型信息的模板类。通过 Traits,可以为不同类型提供统一的接口或元数据,常用于模板元编程中处理类型相关的逻辑。

2. 示例:累加序列(Accumulating a Sequence)

假设需要对不同类型的数组元素求和,但累加结果的类型可能不同(如 int 的累加可能需要 long,而 double 直接累加)。通过 Traits 定义每种类型的累加结果类型和初始值。

#代码实现

#include <iostream>

// 声明 Traits 模板
template<typename T>
struct AccumulationTraits;

// 特化 int 类型的 Traits
template<>
struct AccumulationTraits<int> {
    using AccT = long;          // 累加结果类型为 long
    static constexpr AccT zero = 0; // 初始值为 0
};

// 特化 double 类型的 Traits
template<>
struct AccumulationTraits<double> {
    using AccT = double;        // 累加结果类型为 double
    static constexpr AccT zero = 0.0; // 初始值为 0.0
};

// 累加函数模板
template<typename T>
typename AccumulationTraits<T>::AccT accumulate(const T* beg, const T* end) {
    using AccT = typename AccumulationTraits<T>::AccT;
    AccT total = AccumulationTraits<T>::zero;
    while (beg != end) {
        total += *beg;
        ++beg;
    }
    return total;
}

// 测试用例
int main() {
    int intArray[] = {1, 2, 3, 4, 5};
    double doubleArray[] = {1.1, 2.2, 3.3};

    auto intSum = accumulate(intArray, intArray + 5);
    auto doubleSum = accumulate(doubleArray, doubleArray + 3);

    std::cout << "Sum of ints: " << intSum << std::endl;    // 输出 15
    std::cout << "Sum of doubles: " << doubleSum << std::endl; // 输出 6.6

    return 0;
}

#编译与运行

g++ -std=c++11 -o test test.cpp && ./test

#输出

Sum of ints: 15
Sum of doubles: 6.6

3. 固定特性(Fixed Traits)

固定特性为特定类型提供固定的元数据。例如,AccumulationTraits 为每种类型定义 AccTzero

关键点

  • 使用模板特化为不同类型定制元数据。
  • static constexpr 定义编译期常量。

4. 值特性(Value Traits)

值特性不仅携带类型信息,还可以包含值。例如,为类型提供默认值。

#代码扩展

// 添加 char 类型的 Traits,累加结果为 int,初始值为 '0' 的 ASCII 值
template<>
struct AccumulationTraits<char> {
    using AccT = int;
    static constexpr AccT zero = '0'; // ASCII 值为 48
};

// 测试用例
int main() {
    char charArray[] = {'a', 'b', 'c'}; // ASCII 值分别为 97, 98, 99

    auto charSum = accumulate(charArray, charArray + 3);
    std::cout << "Sum of chars: " << charSum << std::endl; // 输出 97+98+99+48*3=?
    // 注意:初始值 zero 被加入 total,但逻辑可能需要调整(此处仅为示例)
    return 0;
}

5. 参数化特性(Parameterized Traits)

允许 Traits 接受参数,增加灵活性。例如,通过模板参数指定累加策略。

#代码示例

// 策略类:普通累加
template<typename T>
struct SumPolicy {
    static void accumulate(T& total, const T& value) {
        total += value;
    }
};

// 策略类:乘积累加
template<typename T>
struct ProductPolicy {
    static void accumulate(T& total, const T& value) {
        total *= value;
    }
};

// 参数化 Traits
template<typename T, typename Policy = SumPolicy<T>>
class Accumulator {
public:
    using AccT = typename AccumulationTraits<T>::AccT;
    
    static AccT accumulate(const T* beg, const T* end) {
        AccT total = AccumulationTraits<T>::zero;
        while (beg != end) {
            Policy::accumulate(total, *beg);
            ++beg;
        }
        return total;
    }
};

// 测试用例
int main() {
    int intArray[] = {1, 2, 3, 4, 5};
    
    // 使用 SumPolicy
    auto sum = Accumulator<int>::accumulate(intArray, intArray + 5);
    std::cout << "Sum: " << sum << std::endl; // 15
    
    // 使用 ProductPolicy(注意初始值需为 1)
    using ProductAccumulator = Accumulator<int, ProductPolicy<int>>;
    auto product = ProductAccumulator::accumulate(intArray + 1, intArray + 5); // 2*3*4*5=120
    std::cout << "Product: " << product << std::endl;
    
    return 0;
}

6. SFINAE-based Traits

利用 SFINAE 在编译期选择不同实现。例如,检查类型是否支持 += 操作。

#代码示例

#include <type_traits>

// 检查类型 T 是否有 += 操作
template<typename T, typename = void>
struct HasPlusEquals : std::false_type {};

template<typename T>
struct HasPlusEquals<T, std::void_t<decltype(std::declval<T&>() += std::declval<const T&>())>> 
    : std::true_type {};

// 根据 HasPlusEquals 选择累加方式
template<typename T>
typename std::enable_if<HasPlusEquals<T>::value, typename AccumulationTraits<T>::AccT>::type
accumulate_sfinae(const T* beg, const T* end) {
    typename AccumulationTraits<T>::AccT total = AccumulationTraits<T>::zero;
    while (beg != end) {
        total += *beg;
        ++beg;
    }
    return total;
}

template<typename T>
typename std::enable_if<!HasPlusEquals<T>::value, typename AccumulationTraits<T>::AccT>::type
accumulate_sfinae(const T* beg, const T* end) {
    // 默认实现,例如乘法或其他操作
    return typename AccumulationTraits<T>::AccT{};
}

// 测试用例
struct Foo {}; // 无 += 操作

int main() {
    int intArray[] = {1, 2, 3};
    Foo fooArray[3];
    
    std::cout << accumulate_sfinae(intArray, intArray + 3) << std::endl; // 6
    std::cout << accumulate_sfinae(fooArray, fooArray + 3) << std::endl; // 0(默认)
    
    return 0;
}

多选题(附答案与详解)

题目1:类型特征的主要作用是什么?
A) 在编译期获取类型属性
B) 实现运行时多态
C) 优化内存分配策略
D) 简化模板参数传递

答案:A, D
详解:
类型特征的核心是通过模板元编程在编译期获取类型属性(如是否为指针、是否有成员函数等),从而实现静态条件判断(D)。它不涉及运行时多态(B),也不直接优化内存分配(C)。


题目2:如何检测类型T是否为指针?
A) std::is_pointer_v<T>
B) std::is_reference_v<T>
C) std::is_array_v<T>
D) std::is_function_v<T>

答案:A
详解:
std::is_pointer_v专门用于检测指针类型。其他选项分别检测引用(B)、数组(C)和函数(D)。


题目3:如何检测类型T是否有成员函数void foo()
A) 使用decltype(&T::foo)结合SFINAE
B) 直接调用t.foo()并捕获异常
C) 利用std::is_member_function_pointer_v<decltype(&T::foo)>
D) 无法在编译期检测

答案:A, C
详解:
A通过偏特化检测成员函数指针的存在;C直接利用标准库类型特征。B依赖运行时异常,不符合编译期检测;D错误,C++11后可通过特征检测实现。


题目4:编译期if constexpr的用途不包括?
A) 条件实例化模板
B) 运行时性能优化
C) 消除无效代码分支
D) 实现递归模板终止条件

答案:B
详解:
if constexpr在编译期求值,仅保留有效分支(A、C、D),无法优化运行时性能(B)。


题目5:实现enable_if的关键技术是?
A) 模板偏特化
B) 函数重载
C) 继承链
D) 枚举类型

答案:A, B
详解:
enable_if通过模板偏特化(A)和函数重载(B)实现条件启用。继承和枚举不适用此场景。


题目6:类型特征与策略类的区别是?
A) 策略类通过继承定制行为
B) 类型特征通过模板参数传递
C) 类型特征在编译期决策
D) 策略类需要虚函数

答案:A, C
详解:
策略类依赖运行时多态(D),而类型特征通过编译期条件(C)和模板参数(B)实现静态定制。


题目7:如何检测类型T是否为整数?
A) std::is_integral_v<T>
B) std::is_floating_point_v<T>
C) std::is_enum_v<T>
D) std::is_pointer_v<T>

答案:A
详解:
std::is_integral_v专用于整数类型检测。其他选项分别检测浮点(B)、枚举(C)和指针(D)。


题目8:如何检测类型T是否有默认构造函数?
A) std::is_default_constructible_v<T>
B) std::has_trivial_constructor_v<T>
C) std::is_constructible_v<T>
D) std::is_move_constructible_v<T>

答案:A
详解:
std::is_default_constructible_v明确检测默认构造函数。B已弃用,C检测可构造性但不限构造方式,D检测移动构造。


题目9:如何检测类型T是否有虚函数?
A) std::is_polymorphic_v<T>
B) std::has_virtual_destructor_v<T>
C) std::is_abstract_v<T>
D) 无法在编译期检测

答案:A
详解:
std::is_polymorphic_v检测类是否包含虚函数。B检测析构函数,C检测抽象类,D错误(C++11支持)。


题目10:组合类型特征的典型场景是?
A) 同时要求类型可拷贝且可比较
B) 实现单例模式
C) 定义线程安全接口
D) 优化内存对齐

答案:A
详解:
组合特征(如std::conjunction)常用于多条件约束(A)。B、C、D与类型特征无关。


设计题(附答案与详解)

题目1:实现类型特征is_pointer_like,检测类型是否为指针或智能指针

#include <iostream>
#include <memory>
#include <type_traits>

template<typename T>
struct is_pointer_like : std::disjunction<std::is_pointer<T>, std::is_member_pointer<T>> {};

template<typename T>
constexpr bool is_pointer_like_v = is_pointer_like<T>::value;

int main() {
    std::cout << std::boolalpha;
    std::cout << is_pointer_like_v<int*> << '\n';          // true
    std::cout << is_pointer_like_v<std::shared_ptr<int>> << '\n'; // true
    std::cout << is_pointer_like_v<int> << '\n';           // false
    return 0;
}

题目2:使用enable_if实现类型安全的加法

#include <iostream>
#include <type_traits>

template<typename T, typename U, 
         std::enable_if_t<std::is_arithmetic_v<T> && std::is_arithmetic_v<U>, int> = 0>
auto safe_add(T a, U b) -> decltype(a + b) {
    return a + b;
}

int main() {
    std::cout << safe_add(1, 2.5) << '\n';  // 3.5
    // safe_add("abc", 123); // 编译错误
    return 0;
}

题目3:检测类型是否具有size()成员函数

#include <iostream>
#include <vector>
#include <string>

template<typename T, typename = void>
struct has_size : std::false_type {};

template<typename T>
struct has_size<T, std::void_t<decltype(std::declval<T>().size())>> : std::true_type {};

template<typename T>
constexpr bool has_size_v = has_size<T>::value;

int main() {
    std::cout << has_size_v<std::vector<int>>() << '\n';  // 1
    std::cout << has_size_v<int>() << '\n';             // 0
    return 0;
}

题目4:使用编译期if实现类型选择器

#include <iostream>
#include <type_traits>

template<typename T>
void process(T value) {
    if constexpr (std::is_integral_v<T>) {
        std::cout << "Integral: " << value << '\n';
    } else if constexpr (std::is_floating_point_v<T>) {
        std::cout << "Floating: " << value << '\n';
    } else {
        std::cout << "Other type\n";
    }
}

int main() {
    process(42);       // Integral
    process(3.14);     // Floating
    process("hello");  // Other
    return 0;
}

题目5:组合特征检测可序列化类型

#include <iostream>
#include <string>
#include <vector>
#include <type_traits>

template<typename T>
concept Serializable = requires(T t) {
    { t.serialize() } -> std::convertible_to<std::string>;
};

template<typename T>
void save(const T& obj) {
    if constexpr (Serializable<T>) {
        std::cout << "Serialized: " << obj.serialize() << '\n';
    } else {
        static_assert(Serializable<T>, "Type must be serializable");
    }
}

struct Data {
    std::string serialize() const { return "Data"; }
};

int main() {
    Data d;
    save(d);  // Serialized: Data
    
    // save(42); // 编译错误
    return 0;
}

总结

  • Traits 提供类型元数据,需通过模板特化定制。
  • 值特性 允许携带编译期常量。
  • 参数化 Traits 通过策略类增强灵活性。
  • SFINAE 实现编译期多态,根据类型特性选择不同实现。

http://www.kler.cn/a/598205.html

相关文章:

  • 持续集成与持续交付:这里有从开发到部署的全流程优化
  • iOS:GCD信号量、同步、异步的使用方法
  • 字节、清华AIR开源DAPO——学习笔记
  • 快速排序总结
  • windows清除电脑开机密码,可保留原本的系统和资料,不重装系统
  • Mybatis操作数据库(注解+xml两个方式)
  • Flutter访问数据库
  • 【Python数据分析+可视化项目案例】:亚马逊平台用户订单数据分析
  • 【动态规划】下降路径最小和
  • KiLog2MaximumIncrement的由来和KiMaximumIncrementReciprocal的由来
  • 2025年第二期 | CCF ODC《开源战略动态月报》
  • 【leetcode hot 100 51】N皇后
  • JVM如何处理Java中的精度转换: 从源码到字节码
  • 坦克大战(c++)
  • list的模拟实现和学习
  • 数据分析异步进阶:aiohttp与Asyncio性能提升
  • 杨辉三角 II(js实现,LeetCode:119)
  • OSPF多区域通信
  • Js闭包Closure 及 其可能产生的内存泄漏问题
  • C++常见问题与思考