探索类型萃取的必要性

首先我们来实现一个能装string的容器,只实现了尾插和打印接口。

template <class T>
class SeqList{
public:
    SeqList()
        :_a(NULL), _size(0), _capacity(0)
    {}

    void PushBack(const T& x)
    {
        if (_size >= _capacity){
            Extend();
        }
        _a[_size++] = x;
    }

    void Print()
    {
        for (size_t i = 0; i < _size; i++){
            cout << _a[i] << " ";
        }
        cout << endl;
    }

    ~SeqList()
    {
        if (_a){
            delete[] _a;
            _size = _capacity = 0;
        }
    }

private:
    void Extend()
    {
        if (_size >= _capacity){
            //扩容时,如果容量为0,那就给它赋值为3,否则就二倍的扩容
            _capacity = _capacity > 0 ? _capacity * 2 : 3;
            T* tmp = new T[_capacity];
            //使用memcpy浅拷贝的形式扩容
            memcpy(tmp, _a, sizeof(T)*_size);
            delete[] _a;
            _a = tmp;
        }
    }

private:
    T* _a;
    size_t _size;
    size_t _capacity;
};

注意:扩容时,现在采用的是memcpy浅拷贝。

接下来进行测试:

void Test()
{
    SeqList<string> s;
    s.PushBack("aaa");
    s.PushBack("bbb");
    s.PushBack("ccc");
    s.PushBack("ddd");
    s.Print();
}

这里写图片描述
由于构造函数里全部初始化为0,第一次扩容时定容量为3,所以在插入第四条数据“ddd”的时候一定会再次扩容。
扩容时,new一个新的空间,大小是之前容量的二倍,然后通过浅拷贝的方式把老空间的数据赋值给新空间,然后delete老空间,_a指向新空间。
这里有一个疑问,string类里不是有一个指针成员指向字符串吗?那么扩容方式如果为浅拷贝,在delete老空间的时候,每一个string对象调用它自己的析构函数,那么”aaa”,”bbb”,”ccc”这些空间就被释放了,所以新空间里string对象的指针不就成野指针了吗?之后不应该崩溃吗?
这里写图片描述
如果这样插入数据:

void Test()
{
    SeqList<string> s;
    s.PushBack("aaa");
    s.PushBack("bbb");
    s.PushBack("cccccccccccccccccccccccccccccc");
    s.PushBack("ddd");
    s.Print();
}

这里写图片描述
程序直接就崩溃了。
难道崩溃不崩溃,还和我插入数据的长度有关吗?
有关
string底层实现机制是:

class string{
......
private:
    char _Buf[16];//如果存的数据小于16字节,就存在数组里,大于16字节才用到指针
    char* _ptr;
    size_t _Mysize;
    size_t _Myres;
......
};

所以才有了刚才怪异的问题,第一个例子,我存的都是小于16字节的数据,都是用数组存的,扩容时,浅拷贝不影响什么;第二个例子,我存了一个很大的数据,这时是用指针指向的,扩容时,浅拷贝当然就崩溃了。

解决办法:
扩容拷贝数据时,使用深拷贝,利用string的operator=

    void Extend()
    {
        if (_size >= _capacity){
            _capacity = _capacity > 0 ? _capacity * 2 : 3;
            T* tmp = new T[_capacity];
            //memcpy(tmp, _a, sizeof(T)*_size);
            for (size_t i = 0; i < _size; i++){
                tmp[i] = _a[i];//利用string的operator=
            }
            delete[] _a;
            _a = tmp;
        }
    }

这里写图片描述
这样就不会崩溃了:
这里写图片描述

然后深拷贝的代价是很大的,需要拷贝数据,最理想的做法是,对于一些没有指针的对象,比如int,char,再比如上文中存数组的string对象,我们可以采用浅拷贝的方法;只有当对象有指针时,才只能使用深拷贝。
关于类型萃取的实现,我在之前有介绍过,附上链接:
https://blog.youkuaiyun.com/han8040laixin/article/details/78575736

<think>好的,用户想了解enable_if和类型萃取。首先,我需要确认这两个概念在C++中的位置,都属于模板元编程的范畴。 enable_if,我记得是用来在模板参数满足某些条件时启用或禁用特定的模板重载。它的实现依赖于SFINAE(替换失败并非错误)原则。当模板参数不符合条件时,该重载会被忽略,编译器会选择其他合适的版本。 类型萃取(Type Traits)则是用来在编译时获取类型的信息或对类型进行转换的工具。比如判断一个类型是否为整数、是否为指针等。标准库中的<type_traits>头文件提供了很多这样的模板类。 接下来需要解释这两个概念如何结合使用。例如,enable_if常常依赖于类型萃取的结果作为条件,来选择性启用函数模板或类模板。 可能需要举例说明。比如,编写一个函数模板,只允许整数类型参数,这时可以用enable_if配合is_integral类型萃取。当条件满足时,函数才会被实例化,否则编译器忽略,避免错误。 还要注意两者的语法细节。enable_if通常作为模板参数的默认参数,或者作为函数返回类型类型萃取则通过成员value或::value来获取布尔结果。 用户可能想知道实际应用场景。比如在重载函数时避免歧义,或者优化特定类型的处理。比如标准库中的advance函数,针对不同的迭代器类别(随机访问、双向等)有不同的实现,通过类型萃取判断迭代器类型,并用enable_if选择对应的实现。 需要确保解释清晰,分步骤说明。先介绍类型萃取,再讲enable_if,再结合例子展示如何一起使用。同时要提醒常见的错误,比如条件书写错误导致的编译失败,或者SFINAE的正确应用方式。 最后总结两者的作用和重要性,强调它们在模板元编程中对代码灵活性和安全性的提升。</think>$\textsf{enable\_if}$ 和 $\textsf{类型萃取}$ 是 C++ 模板元编程中的两个重要工具,用于实现 $\textsf{编译期条件判断}$ 和 $\textsf{类型特性检查}$。以下是逐步解释: --- ### 一、类型萃取 (Type Traits) $\textsf{类型萃取}$ 用于在编译期获取类型特征,标准库在 $\texttt{<type\_traits>}$ 中提供预定义工具: #### 1. 基础示例 ```cpp // 判断类型是否为整数 static_assert(std::is_integral<int>::value, "int是整型"); // 通过 static_assert(!std::is_floating_point_v<int>, "非浮点型"); // C++17简化写法 ``` #### 2. 工作原理 通过模板特化实现: ```cpp template<typename T> struct is_pointer { static constexpr bool value = false; }; template<typename T> struct is_pointer<T*> { // 针对指针类型的特化版本 static constexpr bool value = true; }; ``` --- ### 二、enable\_if 的作用 $\textsf{enable\_if}$ 用于根据条件 $\textsf{选择性启用}$ 函数模板或类模板的重载,基于 $\textsf{SFINAE}$ 原则(替换失败不是错误)。 #### 1. 标准库定义 ```cpp template<bool B, typename T = void> struct enable_if {}; template<typename T> struct enable_if<true, T> { // 当条件为true时定义type成员 using type = T; }; ``` #### 2. 典型用法 ```cpp // 仅当T是整型时启用该函数 template<typename T> typename std::enable_if<std::is_integral<T>::value, void>::type process(T num) { // 处理整型 } ``` --- ### 三、组合应用案例 #### 1. 函数模板重载控制 ```cpp // 版本1:处理整型 template<typename T> std::enable_if_t<std::is_integral_v<T>, void> handle(T val) { std::cout << "整型处理" << std::endl; } // 版本2:处理浮点型 template<typename T> std::enable_if_t<std::is_floating_point_v<T>, void> handle(T val) { std::cout << "浮点型处理" << std::endl; } ``` #### 2. 类模板特化控制 ```cpp template<typename T, typename = void> class MyContainer; // 基础模板不实现 // 仅当T可拷贝时启用特化版本 template<typename T> class MyContainer<T, std::enable_if_t<std::is_copy_constructible_v<T>>> { // 实现代码... }; ``` --- ### 四、关键差异总结 | 特性 | 类型萃取 | enable\_if | |---------------------|----------------------------------|--------------------------------| | **目的** | 检查/修改类型特征 | 根据条件启用/禁用代码 | | **输出形式** | 通过::value或::type提供结果 | 通过::type存在性控制代码生成 | | **典型应用场景** | 类型分类、特征判断 | 函数重载选择、模板特化控制 | --- ### 五、实际应用场景 1. **算法优化**:对迭代器类型进行判断(如$\texttt{std::advance}$) 2. **接口约束**:限制模板参数类型(如只接受算术类型) 3. **元编程**:编译期生成不同类型代码 通过 $\textsf{enable\_if + 类型萃取}$ 的组合,可实现 $\textsf{零运行时开销}$ 的类型安全控制,这是 C++ 模板元编程的核心技术之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值