美文网首页C++ Templates
【C++ Templates(9)】模板实战

【C++ Templates(9)】模板实战

作者: downdemo | 来源:发表于2018-04-24 17:18 被阅读41次

包含模型

  • 对于非模板代码通常
    • 类和其他类型都放在一个头文件中
    • 全局变量和非内联函数,声明放在头文件中,定义放在dot-C文件(.cpp)中
  • 在习惯这种约定后,对模板使用,编译器会接受,但链接器会产生一个错误,表示没有找到函数定义
// basics/myfirst.hpp

#ifndef MYFIRST_HPP
#define MYFIRST_HPP

// declaration of template
template<typename T>
void printTypeof (T const&);

#endif   //MYFIRST_HPP


// basics/myfirst.cpp

#include <iostream>
#include <typeinfo>
#include "myfirst.hpp"

// implementation/definition of template
template<typename T>
void printTypeof (T const& x)
{
    std::cout << typeid(x).name() << '\n';
}

// basics/myfirstmain.cpp

#include "myfirst.hpp"

// use of the template
int main()
{
    double ice = 3.0;
    print_typeof(ice); // call function template for type double
}
  • 原因是函数模板定义还没实例化,编译器看到函数调用时,还没看到基于double实例化的函数定义,只是假设在别处提供了定义,并产生一个指向该定义的引用,让链接器利用该引用解决此问题
  • 通常采用对待宏或内联函数的解决方案,是把模板定义也包含在声明的头文件中,让声明和定义位于同一头文件,对于上述问题有三个解决方案
    • 把#include "myfirst.cpp"添加到myfirst.hpp末尾
    • 在每个使用模板的dot-C文件中包含myfirst.cpp
    • 不要myfirst.cpp,重写myfirst.hpp,让它包含模板声明和定义
// basics/myfirst2.hpp

#ifndef MYFIRST_HPP
#define MYFIRST_HPP

#include <iostream>
#include <typeinfo>

// declaration of template
template<typename T>
void printTypeof (T const&);

// implementation/definition of template

template<typename T>
void printTypeof (T const& x)
{
    std::cout << typeid(x).name() << '\n';
}

#endif   //MYFIRST_HPP
  • 模板的这种组织方式称为包含模型
  • 包含模型最大的不足之处是增加了包含头文件的开销,例子中主要的开销不是取决于模板定义本身大小,而是模板定义中包含的头文件(<iostream><typeinfo>)的大小,这样会带来成千上万行代码,因为如<iostream>本身也包含了许多类似的模板定义

显式实例化

  • template后接需要实例化的实体(类、函数、成员函数)的声明
template void print_typeof<double>(double const&);
// 基于int显式实例化函数模板max()
template int const& max(int const&, int const&);
  • 实例化类模板,可以同时实例化所有类成员,前面已经显式实例化过的成员就不能再次显式实例化
// 基于int显式实例化的MyClass构造函数
template MyClass<int>::MyClass();
// 对于前面已经显式实例化过的成员函数不能再次显式实例化
template MyClass<std::string>::MyClass(); // 错误
  • 对于每个不同实体,一个程序中最多只能有一个显式实例化体,否则会出现链接错误:发现了实例化的重复定义。人工实例化要跟踪每个实体,对于大项目,这种跟踪负担巨大,因此不建议使用。优点是实例化可以在需要的时候进行,能精确控制模板实例的准确位置
  • 显式实例化不会优先匹配重载解析
template<typename T>
void f(T, T)
{}

template void f<double>(double, double); // 显式实例化

f(1, 1.2); // 错误:仍调用非实例化的原始版本,推断类型不一致
f(1.2, 1.2); // OK
f(1, 1); // OK

整合包含模型和显式实例化

  • 根据实际情况自由选择包含模型或显式实例化
// stack.hpp
#ifndef STACK_HPP
#define STACK_HPP

#include <vector>

template <typename T>
class Stack {
private:
    std::vector<T> elems;
public:
    Stack();
    void push(T const&);
    void pop();
    T top() const;
};


// stackdef.hpp
#ifndef STACKDEF_HPP
#define STACKDEF_HPP

#include "stack.hpp"
void Stack<T>::push(T const& elem)
{
    elem.push_back(elem);
}
...

#endif
  • 希望使用包含模型就包含stackdef.hpp,希望显式实例化模板就包含stack.hpp,再提供一个含有所需要显式实例化指示符的dot-C文件
// 显式实例化
// stacktest1.cpp
#include "stack.hpp"
#include <iostream>
#include <string>

int main()
{
    Stack<int> intStack;
    intStack.push(42);
    std::cout << intStack.top() << std::endl;
    intStack.pop();

    Stack<std::string> stringStack;
    stringStack.push("hello");
    std::cout << stringStack.top() << std::endl;
}


// 包含模型
// stack_inst.cpp
#include "stackdef.hpp"
#include <string>

// instantiate class Stack<> for int
template Stack<int>;

// instantiate member functions of Stack<> for strings
template Stack<std::string>::Stack();
template void Stack<std::string>::push(std::string const&);
template std::string Stack<std::string>::top() const;

分离模型(已淘汰)

  • 模板类声明头文件和实现文件分离,方法是在一个文件中定义模板,在模板的定义和声明前加上关键字export,export不能和inline一起使用
// 头文件
#ifndef MYFIRST_HPP
#define MYFIRST_HPP

export
template <typename T>
void print_typeof(T const&);

#endif
  • 但是export关键字实际上在很多编译中都不支持,C++11中去除了export,改为extern。模板的声明和定义不要分离,定义也应该写在头文件中

模板和内联

  • 函数模板可以声明为inline或constexpr,说明符放在模板参数列表之后
template <typename T>
inline T min(const T&, const T&);
  • 然而编译器可能忽略这种内联,函数模板是否内联取决于编译器的优化策略

预编译头文件

  • 即使不存在模板,C++头文件也可以非常巨大,从而需要很长的编译时间,于是一种称为预编译头文件的机制实现了,该机制位于标准范围外,依赖于特定产品实现
  • 预编译头文件机制主要依赖于可以使用某种方式组织代码,让多个文件中前面的代码都是相同的,这样可以先编译完这些代码,并把编译器在这一点的状态存储到一个预编译头文件(通常为.pch)中,对于剩下文件的编译只需要先加载上面已经保存的状态,从下一行开始编译就可以了
  • 重新加载已保存的状态很快,但第一次编译并保存这个状态比编译这些代码慢,增加的时间代价根据实际情况在20%到200%不等
  • 对于被包含的头文件,注意#include包含顺序,不同顺序不能使用预编译头文件
  • 管理预编译头文件一种可取的方法是根据头文件的使用频率和稳定性分层,对于那些不会发生变化的头文件进行预编译。但如果在大型项目中,对所有文件预编译的时间可能比其能节省的时间多,因此关键在于对更稳定的头文件进行预编译,在不太稳定的头文件中重用这个稳定的预编译头文件

相关文章

网友评论

    本文标题:【C++ Templates(9)】模板实战

    本文链接:https://www.haomeiwen.com/subject/iewjlftx.html