跳到主要内容

C++ 模板默认参数

什么是模板默认参数

在C++中,模板是一种强大的功能,它允许我们编写通用的代码,可以适用于多种数据类型。而模板默认参数则是对模板功能的进一步扩展,它允许我们为模板参数指定默认值,当用户在使用模板时没有指定该参数值时,将使用默认值。

模板默认参数的引入使得模板更加灵活,同时也简化了模板的使用。这与普通函数的默认参数概念非常相似,但应用于类型参数或非类型参数模板。

模板默认参数的基本语法

类模板默认参数

类模板的默认参数语法如下:

cpp
template <typename T = DefaultType>
class ClassName {
// 类的实现
};

其中,DefaultType 是当用户不指定类型时将使用的默认类型。

函数模板默认参数

函数模板的默认参数语法如下:

cpp
template <typename T = DefaultType>
ReturnType functionName(parameters) {
// 函数的实现
}

简单示例

让我们看一个简单的类模板默认参数示例:

cpp
#include <iostream>
#include <string>

// 带有默认参数的类模板
template <typename T = int>
class Container {
private:
T value;
public:
Container(T val) : value(val) {}

void display() {
std::cout << "Value: " << value << std::endl;
}

T getValue() const {
return value;
}
};

int main() {
// 使用默认类型 int
Container<> intContainer(42);
intContainer.display();

// 显式指定类型为 double
Container<double> doubleContainer(3.14);
doubleContainer.display();

// 显式指定类型为 string
Container<std::string> stringContainer("Hello");
stringContainer.display();

return 0;
}

输出:

Value: 42
Value: 3.14
Value: Hello

在这个例子中,我们定义了一个名为 Container 的类模板,它有一个默认类型参数 int。当我们使用 Container<> 时,由于未指定类型,编译器会使用默认类型 int

多个模板参数的默认值

C++允许为多个模板参数设置默认值,但需要遵循一个重要规则:如果一个模板参数有默认值,那么它右侧的所有模板参数也必须有默认值。这与函数默认参数的规则相同。

cpp
#include <iostream>

// 有多个默认参数的模板
template <typename T = int, typename U = double, int Size = 10>
class MultiContainer {
private:
T tValue;
U uValue;
int size;
public:
MultiContainer(T t, U u) : tValue(t), uValue(u), size(Size) {}

void display() {
std::cout << "T value: " << tValue << std::endl;
std::cout << "U value: " << uValue << std::endl;
std::cout << "Size: " << size << std::endl;
}
};

int main() {
// 使用所有默认参数
MultiContainer<> container1(5, 7.5);
container1.display();

// 只指定第一个参数
MultiContainer<char> container2('A', 12.34);
container2.display();

// 指定前两个参数
MultiContainer<float, char> container3(3.14f, 'Z');
container3.display();

// 指定所有参数
MultiContainer<bool, int, 5> container4(true, 100);
container4.display();

return 0;
}

输出:

T value: 5
U value: 7.5
Size: 10
T value: A
U value: 12.34
Size: 10
T value: 3.14
U value: Z
Size: 10
T value: 1
U value: 100
Size: 5

函数模板中的默认参数

函数模板也可以使用默认参数,这使得函数模板更加灵活:

cpp
#include <iostream>
#include <string>

// 带有默认参数的函数模板
template <typename T = int>
void printValue(T value) {
std::cout << "Value: " << value << std::endl;
}

// 带有多个模板参数的函数模板
template <typename T = int, typename U = std::string>
void printPair(T first, U second) {
std::cout << "First: " << first << ", Second: " << second << std::endl;
}

int main() {
// 使用默认类型 int
printValue(42);

// 显式指定类型
printValue<double>(3.14);
printValue<std::string>("Hello");

// 使用默认类型参数的组合
printPair(10, "Ten");
printPair<double>(3.14, "Pi");
printPair<char, int>('A', 65);

return 0;
}

输出:

Value: 42
Value: 3.14
Value: Hello
First: 10, Second: Ten
First: 3.14, Second: Pi
First: A, Second: 65
备注

在C++17之前,函数模板不能部分指定模板参数。这意味着你要么使用所有默认参数,要么显式指定所有参数。但在C++17中,引入了函数模板的部分参数推导,使得这一限制得到了缓解。

实际应用场景

自定义容器

模板默认参数在自定义容器类中非常有用。例如,我们可以创建一个通用的动态数组类,默认情况下使用 int 类型:

cpp
#include <iostream>
#include <vector>

template <typename T = int>
class DynamicArray {
private:
std::vector<T> data;
public:
void add(const T& item) {
data.push_back(item);
}

void display() const {
for (const auto& item : data) {
std::cout << item << " ";
}
std::cout << std::endl;
}

T& operator[](size_t index) {
return data[index];
}

size_t size() const {
return data.size();
}
};

int main() {
// 使用默认类型 int
DynamicArray<> intArray;
intArray.add(5);
intArray.add(10);
intArray.add(15);

std::cout << "Integer Array: ";
intArray.display();

// 使用字符串类型
DynamicArray<std::string> stringArray;
stringArray.add("Hello");
stringArray.add("World");
stringArray.add("C++");

std::cout << "String Array: ";
stringArray.display();

return 0;
}

输出:

Integer Array: 5 10 15 
String Array: Hello World C++

默认内存分配器

在容器类中,经常需要使用内存分配器。我们可以通过模板默认参数为容器指定默认的分配器:

cpp
#include <iostream>
#include <memory>
#include <string>

// 简化的自定义容器,带有默认分配器
template <
typename T,
typename Allocator = std::allocator<T>
>
class CustomVector {
private:
T* data;
size_t capacity;
size_t length;
Allocator alloc;

public:
CustomVector() : data(nullptr), capacity(0), length(0) {}

~CustomVector() {
if (data) {
for (size_t i = 0; i < length; ++i) {
alloc.destroy(data + i);
}
alloc.deallocate(data, capacity);
}
}

void push_back(const T& value) {
if (length == capacity) {
size_t new_capacity = capacity == 0 ? 1 : capacity * 2;
T* new_data = alloc.allocate(new_capacity);

for (size_t i = 0; i < length; ++i) {
alloc.construct(new_data + i, data[i]);
alloc.destroy(data + i);
}

if (data) {
alloc.deallocate(data, capacity);
}

data = new_data;
capacity = new_capacity;
}

alloc.construct(data + length, value);
++length;
}

T& operator[](size_t index) {
return data[index];
}

size_t size() const {
return length;
}

void display() const {
for (size_t i = 0; i < length; ++i) {
std::cout << data[i] << " ";
}
std::cout << std::endl;
}
};

int main() {
// 使用默认分配器
CustomVector<int> vec1;
vec1.push_back(10);
vec1.push_back(20);
vec1.push_back(30);

std::cout << "Vector 1: ";
vec1.display();

// 使用默认分配器的字符串容器
CustomVector<std::string> vec2;
vec2.push_back("Hello");
vec2.push_back("C++");
vec2.push_back("Templates");

std::cout << "Vector 2: ";
vec2.display();

return 0;
}

输出:

Vector 1: 10 20 30 
Vector 2: Hello C++ Templates

设置默认比较器

在需要排序或比较元素的容器中,我们可以设置默认的比较器:

cpp
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

template <
typename T,
typename Compare = std::less<T>
>
class SortedContainer {
private:
std::vector<T> data;
Compare comp;

public:
SortedContainer() {}

void insert(const T& value) {
// 找到应该插入的位置
auto it = std::lower_bound(data.begin(), data.end(), value, comp);
data.insert(it, value);
}

void display() const {
for (const auto& item : data) {
std::cout << item << " ";
}
std::cout << std::endl;
}

size_t size() const {
return data.size();
}
};

int main() {
// 使用默认比较器 (升序)
SortedContainer<int> ascending;
ascending.insert(5);
ascending.insert(2);
ascending.insert(8);
ascending.insert(1);

std::cout << "Ascending order: ";
ascending.display();

// 使用自定义比较器 (降序)
SortedContainer<int, std::greater<int>> descending;
descending.insert(5);
descending.insert(2);
descending.insert(8);
descending.insert(1);

std::cout << "Descending order: ";
descending.display();

return 0;
}

输出:

Ascending order: 1 2 5 8 
Descending order: 8 5 2 1

注意事项与最佳实践

注意事项

  1. 模板参数的默认顺序:如果为一个模板参数指定了默认值,那么其右侧的所有模板参数也必须有默认值。

  2. 特化时的默认参数:在模板特化中,不能使用默认模板参数。

  3. 命名冲突:当使用默认模板参数时,要注意可能的命名冲突。

最佳实践

  1. 为常用类型设置默认值:在设计模板时,考虑用户最可能使用的类型,并将其设置为默认值。

  2. 合理组织模板参数顺序:将最不可能需要自定义的参数放在最右侧,这样用户在大多数情况下只需指定前几个参数。

  3. 文档化默认参数:在类或函数文档中清楚地说明默认参数的类型和行为,以帮助用户理解。

  4. 保持向后兼容性:当修改模板代码时,尽量不要更改现有的默认参数,以避免破坏现有代码。

总结

C++模板默认参数是模板编程中一个强大的特性,它允许我们为模板参数指定默认值,使模板更加灵活和易于使用。我们可以为类模板、函数模板设置默认参数,并且可以给多个参数设置默认值。

模板默认参数在实际应用中非常有用,例如在自定义容器中设置默认的数据类型、内存分配器或比较器。通过合理使用模板默认参数,我们可以设计出既灵活又易用的通用组件。

要正确使用模板默认参数,需要注意一些规则和最佳实践,如参数默认顺序、特化时的限制等。掌握这些知识点将帮助你更好地使用C++模板,编写出更加通用和高效的代码。

练习

  1. 创建一个带有默认参数的函数模板 max_value,用于返回两个值中的较大值。默认类型为 int

  2. 创建一个简单的栈类 Stack,使用模板默认参数设置元素类型默认为 int,并且有一个默认容量参数 10

  3. 修改上面的 SortedContainer 类,添加一个默认参数用于指定初始容量。

  4. 创建一个键值对容器 KeyValueStore,其中键的默认类型为 std::string,值的默认类型为 int

  5. 思考:为什么在C++标准库中,std::vector 的第二个模板参数(分配器)是默认参数,而不是第一个参数(元素类型)?

附加资源

通过学习模板默认参数,你已经迈出了掌握C++模板编程的重要一步。继续探索其他模板相关概念,如模板特化、可变参数模板等,将帮助你成为更熟练的C++程序员!