C++模板新手详解(上)-CSDN博客

编写通用模板代码重要技巧

来源:vector 类模拟实现学习vector类

const T& val = T()

以下是vector类模拟实现中 resize() 部分代码:

/* resize */
// val不能给0,因为不知道T的类型,所以给一个T的缺省值
void resize(size_t n, const T& val = T())
{
        // 缩小size
        if (n < size())
                _finish = _start + n;
        else // 增大size
        {
                // 假如需要扩容
                if (n > capacity())
                {
                        reserve(n);
                }

                while (_finish < _start + n)
                {
                        *_finish = val;
                        ++_finish;
                }
        }
}

const T& val = T() 的含义

这行代码实际上包含了三个关键概念:

1. T() - 值初始化
  • T() 表示创建一个类型为 T 的临时对象,并使用值初始化

  • 对于内置类型(如 int, double, 指针 等),T() 会将其初始化为零值:

    • int()0

    • double()0.0

    • char()'\0'

    • 指针()nullptr

  • 对于类类型,T() 会调用默认构造函数

2. const T& - 常量引用
  • 使用常量引用可以避免不必要的对象拷贝

  • 常量引用可以绑定到临时对象(如 T() 创建的对象)

3. = T() - 默认参数
  • 这是函数的默认参数,如果调用时不提供这个参数,就使用 T() 作为默认值

为什么要这样设计?

通用性

由于 vector 是模板类,需要处理任意类型 T

  • 如果是 vector<int>T() 就是 0

  • 如果是 vector<string>T() 就是空字符串

  • 如果是 vector<MyClass>T() 会调用 MyClass 的默认构造函数

示例代码:
vector<int> v1;
v1.resize(10);        // 使用默认值 int() = 0,所有新元素初始化为0

vector<double> v2;
v2.resize(5);         // 使用默认值 double() = 0.0

vector<string> v3;
v3.resize(3);         // 使用默认值 string() = 空字符串// 也可以显式指定值
v1.resize(15, 100);   // 新元素初始化为100
v3.resize(5, "hello");// 新元素初始化为"hello"

为什么不能直接用 0

为什么不能写成 void resize(size_t n, const T& val = 0)

这是因为:

  1. 类型不匹配:如果 Tstring0 不是有效的字符串值

  2. 缺乏通用性:不是所有类型都能从 0 构造

  3. 编译错误:对于不能从 0 转换的类型,代码无法编译

T() 为每种类型提供了最合适的"零值"或默认值,保证了代码的通用性和类型安全。

总结

const T& val = T() 是一个巧妙的设计:

  1. 通用:适用于任何类型 T

  2. 高效:使用引用避免拷贝

  3. 安全:提供类型安全的默认值

  4. 灵活:允许调用者提供自定义值,也有合理的默认值

这种写法在STL和现代C++库中非常常见,是编写通用模板代码的重要技巧之一。

非类型模板参数

模板参数分类类型形参与非类型形参

类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称

非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用

/* 非类型模板参数 */
/* <类型参数, 非类型模板参数> */
template<class T, int N>
class Array
{
public:
        // ...
private:
        T _a[N];
};

int main()
{
        Array<int, 100> a1;                // 100 个 int 类型空间的静态数组
        Array<int, 1000> a2;        // 1000 个 int 类型空间的静态数组

        return 0;
}

注意:

  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的

  2. 非类型的模板参数必须在编译期就能确认结果

2. 模板的特化

template<class T1, class T2>
class Data
{
public:
        Data() { cout << "原模板类:Data<T1,T2>" << endl; }
private:
        T1 _d1;
        T2 _d2;
};

// 全特化 全部参数都特化
template<>
class Data<int, char>
{
public:
        Data() { cout << "全特化:Data<int,char>" << endl; }
private:
};

// 偏特化
template<class T2>
class Data<int, T2>
{
public:
        Data() { cout << "偏特化:Data<int,T2>" << endl; }
private:
};
// 偏特化
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
        Data() { cout << "偏特化:Data<T1*,T2*>" << endl; }
private:
};
// 偏特化
template<class T1, class T2>
class Data<T1&, T2&>
{
public:
        Data() { cout << "偏特化:Data<T1&,T2&>" << endl; }
private:
};

示例使用:

void test2()
{
        // 可以特化很多个模板
        Data<int, int> d1;
        Data<int, double> d2;
        Data<int, char> d3;
        Data<char, char> d4;
        Data<char*, char*> d5;
        Data<char&, char&> d6;
}

3. 分离编译

项目工程中一般将函数或者类的声明放到.h,将函数或者类的定义放到.cpp,这么做是为了方便查看和维护。

但是,模板不能分离编译。

同样是分离编译,普通函数/类可以,函数模板/类模板为什么不行?

3.3 解决方法

1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。推荐使用这种。

2. 模板定义的位置显式实例化。这种方法不实用,不推荐使用。

【分离编译扩展阅读】 http://blog.csdn.net/pongba/article/details/19130

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐