例举C++11 常用模板
一.变参模板
当我们需要处理不定数量的参数时,C++的变参模板(variadic templates)提供了一种灵活的解决方案。变参模板允许我们定义接受任意数量参数的函数或类模板。
类型模板形参包语法:
template <class... T>
void f(T... args)
使用:
#include <iostream>
using namespace std;
template<typename... Args> //模板参数包
void f(Args... args) //函数参数包
{
cout<<" "<<sizeof...(args)<<endl;
}
int main()
{
f(1);
f(1, 2.1, "aaa");
return 0;
}
typename... Args:这是一个模板参数包(template parameter pack)的语法,用于接受任意数量的类型参数,并将它们打包成一个参数包 Args
Args... rest:这是一个函数参数包(function parameter pack)的语法,用于接受任意数量的函数参数,并将它们打包成一个参数包 rest
rest...:这是一个展开语法(unpacking syntax),用于将参数包 rest 展开为一系列参数。
在示例中,Args 表示一组类型参数,
rest 表示一组函数参数,
rest... 将参数包 rest 展开为一系列参数,然后作为新的参数传递给函数。
1.1. 通过递归函数来展开参数包
通过此方法进行展开,需要一个参数包展开的函数和一个递归终止函数.
#include <iostream>
using namespace std;
void f() //函数参数包
{
cout<<" out over"<<endl;
}
template<typename H, typename... T> //模板参数包
void f(H head, T... args) //函数参数包
{
cout<<" "<<head<<endl;
f(args...);
}
int main()
{
f(1, 2.1, "aaa");
return 0;
}
注意:C++17使用折叠表达式后不需要递归终止函数
如果是类似make_unique库函数,也不需要递归终止函数。例如:
// 添加模式标志flag
template <typename T, typename... Args>
pattern_formatter &add_flag(char flag, Args &&...args) {
// 利用变长数据构造T对象, 插入map类型的custom_handlers_, 索引是字符fla
custom_handlers_[flag] = details::make_unique<T>(std::forward<Args>(args)...);
return *this;
}
1.2 是通过逗号表达式来展开参数包
#include <iostream>
using namespace std;
template<typename T>
void f(T t) //函数参数包
{
cout<<" "<<t<<endl;
}
template<typename... Args>
void test(Args... arg)
{
int arr[] = {(f(arg), 0)...};
}
int main()
{
test(1,2,3,4);
}
上述示例中,通过初始化列表来初始化一个变长数组
{(f(arg), 1)...}被展开为 (f(arg1), 0),(f(arg2), 0),(f(arg3), 0)等,由于是一个逗号表达式,在创建数组的时候被赋值为0.
1.3 折叠语法展开(c++17引入)
C++17引入了许多新特性,其中之一就是折叠表达式(Fold Expressions)。折叠表达式是一种新的语法结构,它允许在模板元编程和编译时计算中进行更简洁和高效地处理参数包(parameter pack)。 折叠表达式的基本语法使用( ... )
的形式,其中...
代表参数包,而圆括号内则是折叠表达式的初始值或者累积值。
使用语法:
template<类型 ... args>
注意:这个类型是有限制的,只能是整型、指针、引用
使用折叠语法:
template<typename Arg> //模板参数包
void printfData(Arg arg)
{
cout<<" "<<arg;
}
template<typename... Args> //模板参数包
void f(Args&&... args) //函数参数包
{
(printfData(forward<Args>(args)),...);
}
int main()
{
f(1, 2.1, "aaa");
return 0;
}
- 折叠表达式只能用于编译时计算,不能用于运行时计算。
- 折叠表达式的结果类型必须在编译时就能确定。
- 折叠表达式不能包含副作用,例如修改变量的值或者进行输入输出操作。
二.别名模板-using
using
关键字在 C++11 之前,using
关键字主要用于类型别名的声明。例如:
typedef int myInt;
using myInt = int;
在 C++11 中,using
关键字引入了新的类型别名声明语法,包括别名模板、类型别名模板、
特化别名模板、特化类型别名mo'b
#include <iostream>
#include <vector>
// 别名模板
template <typename T>
using myAlias = std::vector<T>;
// 类型别名模板
template <typename T>
struct myTypeAlias {
using type = std::vector<T>;
};
// 特化别名模板
template <>
using myAlias<int> = std::vector<int>;
// 特化类型别名模板
template <>
struct myTypeAlias<int> {
using type = std::vector<int>;
};
int main() {
myAlias<double> myVector1; // 使用别名模板
myVector1.push_back(1.23);
myVector1.push_back(4.56);
std::cout << "Size of myVector1: " << myVector1.size() << std::endl;
typename myTypeAlias<double>::type myVector2; // 使用类型别名模板
myVector2.push_back(7.89);
myVector2.push_back(0.12);
std::cout << "Size of myVector2: " << myVector2.size() << std::endl;
myAlias<int> myVector3; // 使用特化的别名模板
myVector3.push_back(1);
myVector3.push_back(2);
std::cout << "Size of myVector3: " << myVector3.size() << std::endl;
typename myTypeAlias<int>::type myVector4; // 使用特化的类型别名模板
myVector4.push_back(3);
myVector4.push_back(4);
std::cout << "Size of myVector4: " << myVector4.size() << std::endl;
return 0;
}
三.tuple
类模板
在C++11 引用std::tuple 类模板,它是一个通用的元组类,可以存储多个不同类型的值。需要注意的是,std::tuple
是一个不可变的数据结构,一旦创建后,其元素的值是不可修改的。
#include <iostream>
#include <memory>
#include <tuple>
using namespace std;
struct MyStruct {
int value;
double weight;
string str1;
};
using MyCustomStruct = MyStruct;
int main()
{
//通过get读取
tuple<int, double, string> mytuple(1,99.9,"hello");
cout<<"mytuple.get:"<<get<0>(mytuple)<<endl;
cout<<"mytuple.get:"<<get<1>(mytuple)<<endl;
cout<<"mytuple.get:"<<get<2>(mytuple)<<endl;
cout<<"sizeof(tuple)"<<sizeof(mytuple)<<endl; //48
cout<<"sizeof(int)"<<sizeof(get<0>(mytuple))<<endl; //4
cout<<"sizeof(double)"<<sizeof(get<1>(mytuple))<<endl; //8
cout<<"sizeof(string)"<<sizeof(get<2>(mytuple))<<endl; //32
cout<<"sizeof(MyStruct)"<<sizeof(MyCustomStruct)<<endl; //48
//通过 结构化绑定解包 tuple 中的值
auto[intVal, douVal, strVal] = mytuple;
cout<<"intVal:"<<intVal<<endl;
cout<<"douVal:"<<douVal<<endl;
cout<<"strVal:"<<strVal<<endl;
//赋值
auto mytuple2 = make_tuple(99, get<2>(mytuple), "nihao");
}
四.bind类模板
std::bind
是 C++ 标准库中的一个函数模板,用于创建函数对象(也称为绑定器),将参数绑定到函数中。它的使用场景包括:
- 绑定函数对象:
std::bind
可以将一个函数对象与其参数绑定,创建一个新的可调用对象。这样,我们可以在稍后的时间点调用这个可调用对象,而不需要再次提供参数。这对于延迟执行函数或将函数作为参数传递非常有用。、 - 重排参数顺序:
std::bind
可以通过重新排列参数的顺序,将函数的参数与绑定的参数进行匹配。这使得我们可以在调用时以不同的顺序提供参数,而不必更改函数的定义。 - 固定部分参数:
std::bind
可以将函数的部分参数固定下来,而不需要提供完整的参数列表。这样,我们可以创建一个新的函数对象,该对象只需要提供剩余的参数即可完成调用。 - 占位符参数:
std::bind
使用std::placeholders::_1
、std::placeholders::_2
等占位符参数,可以在绑定函数对象时指定参数的位置。这使得我们可以在调用时动态地提供参数,而不需要提前确定参数
1.bind绑定非成员函数
#include <iostream>
#include <functional>
using namespace std;
int sum(int a, int b)
{
return a + b;
}
int main()
{
auto func = std::bind(sum, 1, placeholders::_1);
cout<<"sum:"<<func(2)<<endl;
}
2.bind绑定成员函数
#include <iostream>
#include <functional>
using namespace std;
class MyClass {
public:
int sum(int a, int b)
{
return a+ b;
}
};
int main()
{
MyClass obj;
auto func = std::bind(&MyClass::sum, &obj, 1, placeholders::_1);
cout<<"sum:"<<func(2)<<endl;
}
3.bind函数使用注意
- bind函数无法绑定一个重载函数的参数,必须显示的绑定重载函数版本
//有这样的重载函数
int good(int);
double good(double);
auto result = std::bind(good,_1); //错误形式,不知道调用哪一个good函数
//正确的做法,但是比较复杂
auto result_1 = std::bind(double(*)(double)good, _1);
//这是更好的方式
auto result_2 = std::bind<double>(good, _1); //指定函数的返回类型
五.function类模板
std::function是一个通用的函数封装器,可以用来存储、复制和调用任何可调用对象(函数、函数指针、成员函数指针、函数对象等)。它可以用于实现回调机制、函数参数传递等。
std::function不管其实例类型是什么样的,其调用形式是一样的,如下:
返回值类型(实参1,实参2,实参3...)
使用方法(列举所有函数调用方法):
#include <iostream>
#include <functional>
using namespace std;
typedef function<int(int)> Functional;
int funcValue(int x)
{
return x;
}
//仿函数
class Functor
{
public:
int operator()(int a)
{
return a;
}
};
//类的成员函数和静态函数
class Func
{
public:
int func(int a)
{
return a;
}
static int staticFunc(int a)
{
return a;
}
};
int main()
{
//普通函数调用
cout<<"value:"<<funcValue(1)<<endl;
//封装普通函数
Functional obj = funcValue;
cout<<"obj.value:"<<obj(2)<<endl;
//lamda表达式
auto lambObj = [](int a)->int{return a;};
cout<<"lambObj.value"<<lambObj(3)<<endl;
//仿函数
Functor obj1;
cout<<"obj1.value:"<<obj1(4)<<endl;
//类成员函数
Func fun;
cout<<"func.value:"<<fun.func(5)<<endl;
//类静态成员函数
cout<<"staticFunc.value:"<<Func::staticFunc(6)<<endl;
//通过bind绑定
auto bindfun = bind(&Func::func, &fun, placeholders::_1);
cout<<"bindfun.value:"<<bindfun(7)<<endl;
}
六.智能引用
智能引用是一种用于管理资源的对象。它们提供了对资源的安全访问,并确保资源在不再需要时被正确释放,使用智能引用的好处:
- 包装你给的对象引用
- 传参数时, 消除复杂对象的拷贝代价
- 不可拷贝对象转换为可拷贝对象(noncopyable, singleton之类的)
对于第二点:
#include <iostream>
#include <functional>
using namespace std;
template<class T>
class ref_wra
{
public:
explicit ref_wra(T &_t):t_(&_t){}
operator T&() const{return *t_;} //将对象转换为T&的引用
T& get() const{return *t_;}
T* get_ptr() const{return t_;}
private:
T *t_;
};
void add(int& num) {
num++;
}
int main()
{
int dight = 10;
ref_wra<int> ref(dight);
int value = ref; //隐式转换operator T&()
ref.get() = 11;
cout<<"3.访问原始对象:"<<ref<<endl;
int *ptr = ref.get_ptr();
cout<<"4.内部维护指针:"<<ref<<endl;
add(ref);
cout<<"5.通过传引用:"<<ref<<endl;
return 0;
}