C++类模板

        和C++函数一样,类也可以把一个或多个类型参数化,标准库中的容器便是典型的例子,下面便是一个模板类的定义:

#include <cstdio>
#include <cstring>
#include <cassert>

template <typename T>
class Array final
{
public:
    Array(void) : m_Data(nullptr), m_Size(0) {}
    
    Array(const T *data, size_t size) : m_Data(nullptr), m_Size(0) {
        if (data != nullptr && size > 0) {
            m_Size = size;
            m_Data = new T[m_Size];
            memcpy(m_Data, data, sizeof(T) * m_Size);
        }
    }
    
    Array(const Array &);
    Array(Array &&);
    Array &operator=(const Array &);
    Array &operator=(Array &&);
    Array &Swap(Array &);
    
    const T *operator()(void) {
        return m_Data;
    }
    
    T operator[](size_t i) {
        assert(m_Data != nullptr && i < m_Size);
        return m_Data[i];
    }
private:
    T *m_Data;
    size_t m_Size;
};

template <typename T>
Array<T>::Array(const Array &other)
: m_Data(nullptr)
, m_Size(0)
{
    if (other.m_Data != nullptr && other.m_Size != 0) {
        m_Size = other.m_Size;
        m_Data = new T[m_Size];
        memcpy(m_Data, other.m_Data, sizeof(T) * m_Size);
    }
}

template <typename T>
Array<T>::Array(Array &&other)
{
    m_Data = other.m_Data;
    other.m_Data = nullptr;
    m_Size = other.Size;
    other.Size = 0;
}

template <typename T>
Array<T> &Array<T>::operator=(const Array &rhs)
{
    if (&rhs == this)
        return *this;
    
    if (m_Data != nullptr)
        delete[] m_Data;
    
    if (rhs.m_Data != nullptr && rhs.m_Size != 0) {
        m_Size = rhs.m_Size;
        m_Data = new T[m_Size];
        memcpy(m_Data, rhs.m_Data, sizeof(T) * m_Size);
    }
    
    return *this;
}

template <typename T>
Array<T> &Array<T>::operator=(Array &&rhs)
{
    if (&rhs == this)
        return *this;
    
    if (m_Data != nullptr)
        delete[] m_Data;
    
    m_Data = rhs.m_Data;
    rhs.m_Data = nullptr;
    m_Size = rhs.Size;
    rhs.Size = 0;
    
    return *this;
    
}

template <typename T>
Array<T> &Array<T>::Swap(Array &other)
{
    auto data = other.m_Data;
    auto size = other.m_Size;
    other.m_Data = m_Data;
    other.m_Size = m_Size;
    m_Data = data;
    m_Size = size;
    return *this;
}

int main(int argc, char **argv)
{
    char buf[] = "0123456789";
    size_t len = strlen(buf);
    Array<char> str(buf, len);
    Array<char> str0;
    str0.Swap(str);
    for (size_t i = 0; i < len; ++i)
        printf("%c", str0[i]);
    printf("\n\n");
    
    return 0;
}

1 类模板的定义

        同C++普通类一样,类模板的成员函数的实现即可以放到类的内部,也可以放到类的外部。这里需要注意两个细节,每个成员函数的实现都必须使用‘template <typename T>’来表示这是一个模板,并且::(域操作符)之前使用Array<T>取代Array,来限制使用的类型。

        如果仔细观察,会发现成员函数的赋值函数的返回值类型必须使用Array<T>,而形参的类型却可以直接使用Array。因为在模板类的内部(包括类的定义,成员函数形参,成员函数体)使用Array等同于Array<T>。

2 类模板的使用

        类模板成员函数只有在被调用时才会被实例化。直接使用文章开始的源码编译生成二进制程序,使用‘objdump --syms array’观察,得到如下的结果:

SYMBOL TABLE:
0000000100003cd4  w    F __TEXT,__text .hidden __ZN5ArrayIcEC1EPKcm
0000000100003d10  w    F __TEXT,__text .hidden __ZN5ArrayIcEC1Ev
0000000100003e34  w    F __TEXT,__text .hidden __ZN5ArrayIcEC2EPKcm
0000000100003ed4  w    F __TEXT,__text .hidden __ZN5ArrayIcEC2Ev
0000000100003d3c  w    F __TEXT,__text __ZN5ArrayIcE4SwapERS0_
0000000100003d94  w    F __TEXT,__text __ZN5ArrayIcEixEm
0000000100000000 g     F __TEXT,__text __mh_execute_header
0000000100003bbc g     F __TEXT,__text _main
0000000000000000         *UND* __Znam
0000000000000000         *UND* ___assert_rtn
0000000000000000         *UND* ___stack_chk_fail
0000000000000000         *UND* ___stack_chk_guard
0000000000000000         *UND* _memcpy
0000000000000000         *UND* _printf
0000000000000000         *UND* _strlen

        而注释掉Swap的调用,如下:

int main(int argc, char **argv)
{
    char buf[] = "0123456789";
    size_t len = strlen(buf);
    Array<char> str(buf, len);
    /*Array<char> str0;
    str0.Swap(str);
    for (size_t i = 0; i < len; ++i)
        printf("%c", str0[i]);
    printf("\n\n");*/
 
    return 0;
}

        再次使用‘objdump --syms array’观察生成的二进制程序,结果如下:

SYMBOL TABLE:
0000000100003e90  w    F __TEXT,__text .hidden __ZN5ArrayIcEC1EPKcm
0000000100003ecc  w    F __TEXT,__text .hidden __ZN5ArrayIcEC2EPKcm
0000000100000000 g     F __TEXT,__text __mh_execute_header
0000000100003dfc g     F __TEXT,__text _main
0000000000000000         *UND* __Znam
0000000000000000         *UND* ___stack_chk_fail
0000000000000000         *UND* ___stack_chk_guard
0000000000000000         *UND* _memcpy
0000000000000000         *UND* _strlen

        仔细观察,会发现swap函数没有被实例化。

        类模板存在静态成员,该静态成员仅需被实例化一次(未完理解这段的含义)。如下:

template <typename T>
class MyType final
{
public:
    static std::string m_TypeName;
    static T m_Elem;
};

template <typename T>
std::string MyType<T>::m_TypeName = "unknow";

template <typename T>
T MyType<T>::m_Elem = T();

3 类型别名

        在c++11之前,仅能通过typedef定义类型别名,如下:

typedef std::vector<int> int_vec;

        c++11开始,又多了一种定义新类型的方式——通过using关键字定义类型别名,如下:

using int_vec = std::vector<int>;

4 类模板的类型推导

        从c++17开始,不用指定模板类型参数,就可以调用类的拷贝构造函数和赋值函数,如下:

std::vector<int> vec0 {1, 2, 3, 4, 5};
std::vector vec1 = vec0;
std::vector vec2(vec0);

        虽然上面这种初始化方式很方便,但个人还是觉得auto更简洁一些:

auto vec3 = vec0;

        如果模板类有一个构造函数仅需要一个参数,并且参数的类型为模板参数,那么可以通过下面的方式直接推导出模板类实例的类型,完成对象的初始化:

template <typename T>
class MyPtr
{
public:
    MyPtr(T data) : m_DataPtr(new T(data)) {}
    
    virtual ~MyPtr(void) {
        if (m_DataPtr != nullptr) {
            delete m_DataPtr;
            m_DataPtr = nullptr;
        }
    }
    
    T *operator->(void)  {
        return m_DataPtr;
    }
    
protected:
    T *m_DataPtr;
};


......


MyPtr pi = 1;

        其实,上面这种方式和隐式类型转换异曲同工,想想std::string str = "hello"。 

5 聚合类的模板化

        聚合类的定义

  • 没有构造函数(包括用户显示定义的构造函数,从基类继承而来的构造函数)
  • 没有private或protected修饰的成员变量(包括从基类继承而来的成员变量)
  • 没有虚函数(包括从基类继承而来的虚函数)

        下面struct ValueWithComment便是一个聚合类:

template <typename T>
struct ValueWithComment {
    T value;
    std::string comment;
};

        使用ValueWithComment定义并初始化一个对象,传统方式如下:

ValueWithComment<int> vc1{10, "ten"};
ValueWithComment<const char *> vc2 = {"10", "ten"};

        当c++17引入类型推断指引后,定义并初始化一个对象变的更加简洁,如下:

ValueWithComment(int, const char *) -> ValueWithComment<int>;

ValueWithComment(char const*, char const*) -> ValueWithComment<std::string>;


......


ValueWithComment vc1{10, "ten"};
ValueWithComment vc2 = {"10", "ten"};

        当然,通过传统方式,使用typedef或define也能够实现类似效果,但看上去远远没有类型推断指引优雅,如下:

#define ValueWithComment_int ValueWithComment<int>
typedef ValueWithComment<std::string> ValueWithComment_string;


......


ValueWithComment_int vc1{10, "ten"};
ValueWithComment_string vc2 = {"10", "ten"};

        使用类型推断指引类型一定要严格遵守聚合类规范,否则编译出错。

  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值