C++ 学习笔记(16)模板、引用折叠、std::forward、可变参数模板、模板特例化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/l773575310/article/details/79337787

C++ 学习笔记(16)模板、引用折叠、std::forward、可变参数模板、模板特例化

参考书籍:《C++ Primer 5th》
API:模板


16.1 定义模版

16.1.1 函数模版

template <typename T> int compare(const T &v1, const T &v2)
{
    if (v1 < v2) return -1;
    if (v2 < v1) return -1;
    return 0;
}

template <typename T, class U> calc (const T&, const U&);       // 每个模版参数都要带 typename 或 class 关键字

template <typename T> inline T func(const T&);      // inline 或 constexpr 说明符 放在模版参数列表之后
  • 非类型模版参数:表示值而非类型。可以是整形,指向对象或函数的指针或左值引用。而绑定的实参必须是常量表达式,绑定到指针或引用实参必须在静态内存里。
// 比较两个字符串
template <unsigned M, unsigned N> int compare(const char(&p1)[M], const char(&p2)[N])
{
    retrun strcpy(p1, p2);
}
  • 函数模版和类模版成员函数的定义通常放在头文件。

16.1.2 类模版

  • 不同于函数模版,编译器无法为类模版推断模版参数类型。
template <typename T> A<T>::A() { }     // A类的函数在类外定义时,也需要指名模版参数
  • 默认情况下,对于一个实例化了类模板,其成员只有在使用时才被实例化。
  • 在类模版作用域内,编译器处理模板自身引用时可以省略模版参数。
template <typename T> class A
{
public:
    // 定义时省略<T> 模版参数
    A& operator++()
    {
        A a = this;     // 使用时也省略参数模版
        return a;
    }
};
template<typename T> using twin = pair<T, T>;   // 使用别名
twin<int> ti;       // ti是 pair<int, int>

16.1.3 模版参数

  • 使用类的类型成员,直接编写其成员,如果类型不包含该成员时,编译器会报错。
// 返回T的value_type成员,声明定义时如果没有该成员,编译时出错
template <typename T> typename T::value_type top(const T& c)
{
    if (!c.empty())     // 执行该类的成员函数,(如果没有,运行错误)
        return c.back();
    else
        return typename T::value_type();    // 若无,值初始化
}

void main()
{
    vector<int> vi = { 3,4,5,2 };
    cout << top<vector<int>>(vi);       // 输出2
}
// 可以使用默认实参,如下,默认比较函数使用标准库的less
template <typename T, typename F =less<T>> int compare(const T &v1, const T &v2, F f = F())
{
    if (f(v1 < v2)) return -1;
    if (f(v2 < v1)) return -1;
    return 0;
}

16.1.4 成员模版

  • 一个类可以包含本身是模版的成员函数,这个成员称为成员模版,不可以是虚函数。
struct Printer 
{
    std::ostream& os;
    Printer(std::ostream& os) : os(os) {}
    template<typename T> void operator()(const T& obj) { os << obj << ' '; }    // 成员模板
};

void main()
{
    std::vector<int> v = { 1,2,3 };
    std::for_each(v.begin(), v.end(), Printer(std::cout));  // 使用临时Printer对象
    Printer pinter = Printer(std::cout);
    std::string s = "abc";
    std::for_each(s.begin(), s.end(), pinter);      // 使用Printer对象
}

16.1.5 控制实例化

  • 模版被使用时才会实例化。多个相同模版就会生成多个实例,可以使用显示实例化避免这种开销,即使用extern声明实例,但也必须实例化定义。
  • 实例化一个类模版时,所有成员都会实例化(包括内联成员函数)。

16.1.6 效率与灵活性

  • unique_ptr在编译时绑定删除器,避免间接调用删除器的运行时开学。
  • shared_ptr在运行时绑定删除器,使用户重载删除器更方便。

16.2 模版实参推断

16.2.1 类型转换与模版类型参数

  • const转换:可以将一个非const对象的引用(或指针)传递给const的形参。
  • 数组或函数指针转换:函数形参不是引用类型时,可以用数组或函数类型做指针转换。(数组转指向第一个元素的指针,函数转指向该函数类型的指针)
  • 传递数组实参时:
    • 实参传递是引用时:数组大小不同时,类型不同。
    • 实参传递非引用时:数组转换成指针,不在乎数组大小。

16.2.2 函数模版显式实参

template <typename T1, typename T2, typename T3> T1 sum1(T2, T3) {}
template <typename T1, typename T2, typename T3> T3 sum2(T2, T1) {}     // 糟糕的设计

void main()
{
    // T1显示指定(必须指定),T2、T3从实参类型中推断。
    auto v1 = sum1<int>(1.0, 2.0);  // int sum<int, double, double>(double, double)

    // 因为T3才是返回值,所以必须要指定T1、T2。
    auto v2 = sum2<float, float, int>(2, 4);    // 正确
    auto v3 = sum3<int>(3, 5);      // 错误,返回值无法推断出来。
}

16.2.3 尾置返回类型与类型转换

// 指名返回值为参数类型的解引用,如果传入int序列,那就是返回int&
template <typename It> auto func1(It beg, It end) -> decltype(*beg) {}

// 使用标准库type_traits的模版,必须使用typename告诉编译器type是类型,传入int序列时,返回int类型值
template <typename It> auto func2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type {}

16.2.5 模版实参推断和引用

  • 右值引用参数:被传入左值时,类型是左值引用。
  • 引用的引用:会被折叠成普通引用。
    • X& &X& &&X&& & 都会折叠成类型 X&
    • X&& && 折叠成 X&&
  • 引用折叠只能用于间接创建的引用的引用,如类型别名或模版参数。
  • 模版参数类型为右值引用时(T&&):可以传递任意类型实参。且传递左值时,会被实例化成左值引用。
  • 右值引用常用于:
    • 模版转发实参。
    • 模版重载。
template <typename T> void f(T&&) {}    // 右值形参

void main()
{
    int i = 123;
    const int ci = 345;
    f(123);     // T为int。(传入右值)
    f(i);       // T为int &。(传入左值)
    f(ci);      // T为const int &。(传入左值)
}

16.2.6 理解std::move

  1. 参数为右值,所以可以传入任何实参(包括左值)。
  2. remove_reference移除参数的左值引用(如果有)。
  3. 使用static_cast:将左值类型转换为右值引用。
template <typename T> typename remove_reference<T>::type && move(T&& t)
{
    return static_cast<typename remove_reference<T>::type&&>(t);
}

16.2.7 转发

  • 使用std::forward保持实参传递的左值/右值属性。
  • 配合传入模版参数类型为右值引用(T&&)时,forward会保持实参类型的所有细节。
// 调用函数f,按t2、t1顺序传入参数。
template <typename F, typename T1, typename T2> void flip(F f, T1 &&t1, T2 &&t2)
{
    // 使用forward,传入实参的左值右值属性都会被保留。
    f(std::forward<T2>(t2), std::forward<T1>(t1));
}

16.3 重载与模板

  • 如果多个函数提供同样好的匹配:
    • 只有一个是非模板函数,选择这个。
    • 没有非模板函数,在多个最特例化的模版。
    • 否则,调用有歧义。
cout << debug("hello") << endl;     // 传入const char *,调用 debug(T *)
// 下面三者都满足,调用优先级依次降低
// debug(T*),T为const char,比下面的更特例化
// debug(const T&),T为char[6]
// debug(const string&),从const char * 转换到 string,可以,但不是精确匹配

16.4 可变参数模板

  • 可变数目的参数成为参数包
    • 模板参数包:零个或多个模板参数。
    • 函数参数包:零个或多个函数参数。
template <typename... Args> void foo(const Args &... rest) 
{
    cout << sizeof...(Args) << endl;    // 类型参数的数目
    cout << sizeof...(rest) << endl;    // 函数参数的数目
}

16.4.1 编写可变参数函数模板

template <typename T,typename... Args> void print(ostream &os,const T& t, const Args &... rest)
{
    os << t << ", ";
    print(os, rest...);     // 递归调用,每次调用减少一个参数(t)
}

// 递归最内层的函数。非可变参数版本。必须有该函数,否则可变参数函数调用出错。
template <typename T> void print(ostream &os, const T& t)
{
    os << t << "!! ";
}

void main()
{
    // 输出:123, abc, 2.333!! 
    print(cout, 123, "abc", 2.333f);
}

16.4.2 包扩展

template <typename... Args> void print(ostream &os, const Args &... rest)
{
    print(os, debug(rest) ...);     // 对每个参数都作为debug()函数的参数,递归调用
}

16.4.3 转发参数包

// 模仿构造函数的参数列表
template <class... Args> void emplace_back(Args&&...);

16.5 模板特例化

  • 模板及其特例化版本应该声明在同一个头文件,所有同名模板的声明应该放在前面,然后是这些模板的特例化版本。
  • 只能部分特例化类模板,不能部分特例化函数模板。
// 版本1:比较任意两个类型
template <typename T> int compare(const T &v1, const T &v2);

// 版本2:处理字符串字面常量,处理的是数组,不能是指针
template <unsigned M, unsigned N> int compare(const char(&p1)[M], const char(&p2)[N]);

// 版本3:模板特例化,可直接处理字符串(compare("sd","ab") )
template <> int compare(const char* const &p1, const char* const &p2);

猜你喜欢

转载自blog.csdn.net/l773575310/article/details/79337787
今日推荐