理解 C++ 中的特征技术(traits)

目录

1.   特征技术概述(Traits)

1.1  特征技术的目的

1.2  特征技术实现方式

1.3  特征技术常见用例

2.   应用举例

3.   创建自己的特征类

3.   C++ 特征对模板逾编程的贡献

3.1  函数模板逾编程

3.2  类模板逾编程


1.   特征技术概述(Traits)

    C++ 特征技术是一种强大的机制,它允许你在编译时获取类型的信息。它使你能够编写能够适应不同类型的泛型代码,而无需运行时开销。

    您可以将特征视为具有多种结果的一个类型函数或一组类型函数。标准库提供了allocator_traitschar_traitsiterator_traitsregex_traitspointer_traits。此外,它还提供了 time_traitstype_traits

    关键概念为:

类型特征(Type Traits)——提供关于类型的属性。

策略(Policy)——将接口定义为对其他类的服务的类或类模板。

模板逾编程(Template Metaprogramming)——使用模板在编译时执行计算(以避免运行时计算,提升运行时性能)。

1.1  特征技术的目的

    以下是关键方面的细分:

(1) 类型自省:

特征技术提供了一种(编译时)查询类型属性的方法,例如某个类型是整数、指针还是类。

(2) 编译时自定义

基于从特征获取的信息,您可以在编译时自定义代码的行为。这允许针对不同类型的代码路径进行优化。

(3) 泛型编程

特征对于编写可处理多种类型的泛型算法和数据结构至关重要。

(4)  扩展功能:

特征技术可以通过提供类型的信息来扩展和调整现有代码

1.2  特征技术实现方式

(1) 模板类

特征通常以模板类的形式实现。它们将类型作为模板参数。

(2) 静态成员:

特征类通常包含静态成员常量或成员类型,用于提供有关类型参数的信息。

(3) 特化

您可以针对特定类型特化特征类,以提供不同的行为或信息。

(4) 编译时估算:

特征在编译时进行求值,因此没有运行时开销。

1.3  特征技术常见用例

(1)  类型检查:

诸如 std::is_integralstd::is_pointer std::is_class 之类的特征允许您检查类型的属性。

(2) 类型转换:

诸如 std::remove_conststd::add_pointer std::remove_reference 之类的特征允许您修改类型。

(3)  算法优化:

通过使用特征,您可以编写能够针对特定类型使用优化的泛型实现的算法,即针对特定类型的模板特化。

(4)  库开发:

特征在 C++ 标准库中被广泛使用,以提供泛型功能。

2.   应用举例

不同的代码片段结构基本相同,但在细节上存在差异,这种情况并不少见。理想情况下,我们能够重用这些结构,并剔除这些差异。在 C 语言中,这可以通过使用函数指针来实现,例如 C 标准库中的 qsort 函数,或者在 C++ 中使用虚函数。遗憾的是,这与编译时已知的运行时情况不同,并且会产生运行时开销。

    C++ 引入了泛型编程,使用模板,消除了运行时绑定的需求,但乍一看,这仍然像是一种妥协,毕竟,相同的算法并非适用于所有数据结构。对链表进行排序与​​对数组进行排序不同。排序数据的搜索速度比未排序数据快得多。

    C++ 特征是模板编程的一个特例,通常只关注特定类型的特性,例如静态属性和静态函数。它是 C++ 模板编程最基本的构建块之一。  

    C 和 C++ 程序员都应该熟悉 limits.h float.h,它们用于确定整数和浮点类型的各种属性。

    大多数 C++ 程序员都熟悉 std::numeric_limits ,乍一看,它只是提供了相同的服务,只是实现方式不同。通过仔细研究numeric_limits,我们发现了 trait 的第一个优点:一致的接口。

    使用 float.h limits.h 时,你必须记住类型前缀和特征,例如, DBL_MAX 包含 double 数据类型的“最大值”特征。通过使用像numeric_limits这样的特征类,类型会成为名称的一部分,因此 double 的最大值将变为numeric_limits<double>::max() ,更重要的是,你不需要知道需要使用哪种类型。例如,以这个简单的模板函数(改编自 [ Veldhuizen ])为例,它返回数组中的最大值:

template< class T >

T findMax(const T const* data,

    const size_t const numItems) {

    // Obtain the minimum value for type T

    T largest =

        std::numeric_limits< T >::min();

    for (unsigned int i = 0; i < numItems; ++i)

        if (data[i] > largest)

            largest = data[i];

    return largest;

}

注意numeric_limits的用法。正如您所见,与C风格的limits.h习语一样,您必须知道类型,而使用C++ 的traits习语,只有编译器需要知道类型。不仅如此,numeric_limits与大多数traits一样,只需通过创建模板的特化版本即可扩展为包含您自己的自定义类型(例如定点或任意精度算术类)。

    我们不讨论numeric_limits,它只是一个trait实际应用的示例,我们介绍如何创建您自己的traits类。

3.   创建自己的特征类

C++ 特征可用于统一不同类型的低级函数接口,从而简化高级模板编程

例如,如果我们想为不同的数值类型(包括 int32_tint64_tfloatdouble)创建函数,例如 greater_than_half_maximum,并使用该类型的最大值,那么如果不使用 特征技术,就很难充分利用模板编程的优势。

template <typename T>

bool greater_than_half_maximum(T value)

{

    return false;

}

template <>

bool greater_than_half_maximum<int32_t>(int32_t value)

{

    // 2^31 - 1

    if (value > 2147483647 / 2)

    {

        return true;

    }

    else

    {

        return false;

    }

}

template <>

bool greater_than_half_maximum<int64_t>(int64_t value)

{

    // 2^63 - 1

    if (value > 9223372036854775807 / 2)

    {

        return true;

    }

    else

    {

        return false;

    }

}

template <>

bool greater_than_half_maximum<float>(float value)

{

    // FLT_MAX defined in cfloat

    if (value > FLT_MAX / 2)

    {

        return true;

    }

    else

    {

        return false;

    }

}

template <>

bool greater_than_half_maximum<double>(double value)

{

    // DBL_MAX defined in cfloat

    if (value > DBL_MAX / 2)

    {

        return true;

    }

    else

    {

        return false;

    }

}

// Even more specializations for different types.

// ...

    从上面的例子我们可以看出,即使我们想使用模板编程,而不使用特征,不同类型的特化实现也是不可避免的。

    std::numeric_limits 是 C++ 标准库中的一个类型特征,它定义了不同内置类型的最大值。通过使用 std::numeric_limits<T>::max(),我们无需针对特定用例为不同类型创建特化。

#include <limits>

template <typename T>

bool greater_than_half_maximum(T value)

{

    if (value > std::numeric_limits<T>::max() / 2)

    {

        return true;

    }

    else

    {

        return false;

    }

}

对于自定义数值类型,例如 NVIDIA __half,我们可以为 std::numeric_limits 创建特化,而不是为 greater_than_half_maximum 创建特化。

namespace std

{

    template <>

    class numeric_limits<__half>

    {

    public:

        constexpr static __half max()

        {

            constexpr uint16_t const half_max_bits{ 0x7BFF };

            __half const half_max{ *reinterpret_cast<__half const*>(&half_max_bits) };

            return half_max;

        };

    };

} // namespace std

当然,有人可能会说,对于不同的类型,我们必须创建不同的类型特征,这同样需要大量的工作。然而,由于低级类型特征通常比高级特化更具可复用性,因此,相比于高级模板特化,在创建低级类型特征上投入精力更有价值。此外,由于高级模板特化通常比类型特征需要更多行实现,因此创建低级类型特征所需的工作量比高级模板特化要少。

3.   C++ 特征对模板逾编程的贡献

    C++ 类型特征对于模板逾编程至关重要。我们将了解它如何用于函数模板逾编程和类模板逾编程。

3.1  函数模板逾编程

    当类型特征与 std::enable_if 一起使用时,我们可以针对不同类型特化函数实现细节,正如我之前的博客文章“使用 Enable If 进行 C++ 模板特化”中所述。

3.2  类模板逾编程

当类型特征与 std::conditional_t 一起使用时,我们可以针对具有不同类型的成员变量且无法简单模板化的类,特化其实现细节。

    例如,C++ <random> 库有两个用于生成均匀分布随机数的函数,包括用于生成整数值的std::uniform_int_distribution和用于生成实数值的 std::uniform_real_distribution

    以下实现使用类型特征 std::is_integral<T>::value 统一了这两个随机数生成函数 std::uniform_int_distribution std::uniform_real_distribution

#include <iostream>

#include <iterator>

#include <random>

#include <vector>

template <typename T>

class UniformDist

{

public:

    UniformDist(T a, T b) : m_min{ a }, m_max{ b }, m_uniform_dist{ a, b } {}

    T operator()(std::mt19937& random_engine)

    {

        return m_uniform_dist(random_engine);

    }

private:

    T const m_min;

    T const m_max;

    using dist_t = std::conditional_t<std::is_integral<T>::value,

        std::uniform_int_distribution<T>,

        std::uniform_real_distribution<T>>;

    dist_t m_uniform_dist;

};

template <typename T>

std::vector<T> create_random_vector(size_t n, T a, T b,

    std::mt19937& random_engine)

{

    UniformDist<T> uniform_dist{ a, b };

    std::vector<T> vec(n, 0.0);

    for (size_t i{ 0 }; i < n; ++i)

    {

        vec[i] = uniform_dist(random_engine);

    }

    return vec;

}

int main()

{

    size_t const n{ 8 };

    unsigned int const seed{ 0U };

    std::mt19937 random_engine{ seed };

    std::vector<double> const random_vector_double{

        create_random_vector(n, -16.0, 16.0, random_engine) };

    std::cout << "Random Vector of Doubles: " << std::endl;

    std::copy(random_vector_double.begin(), random_vector_double.end(),

        std::ostream_iterator<double>(std::cout, " "));

    std::cout << std::endl;

    std::vector<int> const random_vector_int{

        create_random_vector(n, -16, 16, random_engine) };

    std::cout << "Random Vector of Integers: " << std::endl;

    std::copy(random_vector_int.begin(), random_vector_int.end(),

        std::ostream_iterator<int>(std::cout, " "));

    std::cout << std::endl;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值