C++ 实际运用Templates(5)---《C++ Templates》

7 篇文章 0 订阅

置入式模型

myfirst.hpp

#ifndef MYFIRST_HPP
#define MYFIRST_HPP

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

myfirst.cpp

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

template <typename T>
void print_typeof(T const& x){
    std::cout<<typeid(x).name<<std::endl;
}

现在我们在一个测试文件中使用这个function template,猜猜是否可以运行成功?
myfirstmain.cpp

#include "myfirst.hpp"
int main(){
    double ice=3.0;
    print_typeof(ice);
}

大多数编译器都可以正常编译上述程序代码,然而大多数连接器会报告一个错误,表示无法找到print_typeof()函数的定义。错误的原因在于,function template print_typeof()定义并没有被实例化。为了实例化template,编译器需要知道以哪个定义式以及那些template arguments对它实例化。然而这两项信息被分开放置于两个分开编译的文件。因此当编译器看到对print_typeof()的调用时候,看不到其定义,无法以double类型进行实例化。
那么这种问题如何解决呢?

把template放进头文件

就像处理macro(宏)和inline函数一样,解决上面问题的常见办法是:把template定义式放到其声明语句所在的头文件中。针对上面上面问题,我们可以有三种方式:
1)在myfirst.hpp最后加入如下一行代码:

#include "myfirst.cpp"

2)在该template的每个.c文件中包含#include “myfirst.cpp”;
3)完全丢弃myfirst.cpp,把声明和定义全部放进myfirst.hpp中:

#ifndef MYFIRST_HPP
#define MYFIRST_HPP
#include <iostream>
#include <typeinfo>

template <typename T>
void print_typeof(T const&);
template <typename T>
void print_typeof(T const& x){
    std::cout<<typeid(x).name()<<std::endl;
}
#endif

以上三组组织方式称为“置入式模型”,现在程序就可以被正常编译、链接和执行了。但这样会在编译期间大大增加编译时间,同时由于置入式模型与inline和macro不同,non-inline函数不在调用端被展开,因此,每当它们被实例化一次,编译器便从头创建一份函数拷贝,由于这个过程完全自动化,可能在在两个不同文件中的创建出同一个template具现化的两份拷贝,而编译器看到同一份函数的两份定义而报错。
需要注意的是,本节中对function template所谈的内容,同样适用于class template成员函数和static成员函数。当然,也适用于member function template中。

显示实例化

myfirstinst.cpp

#include "myfirst.cpp"
//明确以double类型将print_typeof()实例化
template void print_typeof<double>(double const&);

这个显示实例化有两部分组成:
1)关键词template;
2)template parameter被完全替换之后的函数声明。

template MyClass<int>::MyClass();
template int const& max(int const&,int const&);
template class Stack<int>;
template Stack<std::string>::Stack();
template void Stack<std::string>::push(std::string const&);
template std::string Stack<std::string>::top() const;

//错误,不能对已经被显示实例化的class的某个成员再次实例化
template Stack<int>::Stack();

在程序中每一个不同物体最多只能有一份具现化,换句话你可以明确具现化出print_typeof<int>和print_typeof<double>,但是程序中只能出现一个。

下面我们举一个例子进行仔细理解:
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;
};
#endif

stackdef.hpp

#ifndef STACKDEF_HPP
#define STACKDEF_HPP
#include <stack.hpp>
template <typename T>
void Stack<T>::push(T const& elem){
    elems.push_back(elem);
}
...
#endif

现在,如果我们想要使用置入式模型,可以简单地将定义是所在的头文件stackdef.hpp包含进来;如果我们想要使用显示实例化,可以将声明语句所咋的头文件stack.hpp包含进来,并提供一个.c文件,其中有必要的显示实例化指令。

stacktest1.cpp

#include "stackdef.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 "stack.hpp"
#include <string>
template class Stack<int>;
template Stack<std::string>::Stack();
template void Stack<std::string>::push(std::string const&);
template std::string Stack<std::string>::top() const;

分离式模型

export使用非常简单,只需要将template定义于某文件中,并将其定义式及所有非定义声明加上关键词export,就可以将头文件和实现文件中的代码导入,因此非常方便。
由于添加了export,不需现场看到template定义文件,即template的使用和定义可分隔与不同的的编译单元中,文件myfirst3.hpp中含有这些成员函数声明就足够了,因为myfirst3.cpp中的代码会被自动导入。

关键词export适用于function template、class template成员函数、member function template、class template的static成员函数,关键词export也可以被用来class template声明语句中,其意义是汇出所有可以被会出的成员。
myfirst3.hpp

#ifndef MYFIRST_HPP
#define MYFIRST_HPP
export temport<typename T>
void print_typeof(T const&);
#endif
export template<typename T>
public:
    void memfun1();//会被汇出
    void memfun2(){//不会被汇出,因为它暗自成为inline
        ...
    }
    void memfun3();//不会被汇出,因为它在下方定义中被明确定义为inline
    ...
};
template <typename T>
inline void MyClass<T>::memfun3(){
    ...
}

需要注意的是,关键字export不能和关键词inline合用,而且关键词export应该总是写在关键词template之前。一下程序代码是不合法的。
错误示例:

//错误,export没有出现在template之前
template <typename T>
class Invalid{
public:
    export void wrong(T);
};
//错误,export与inline合用
export template <typename T>
inline void Invalid<T>::wrong<T>{

}
//错误,export与inline合用
export template <typename T>
inline T const& max(T const&a,T const&b){
    return a<b?b:a;
}

既然export有这么多好处,为什么我们仍然推荐使用”置入式模型”呢?当然是因为export会带来一些问题。
export需要处理两个问题:何时具现template,以及template被定义于何处。因此尽管代码角度没有任何关系然而幕后却使得其有一种不可见的耦合关系。

为了可以随时在“置入式模型”和“分离式模型”之间灵活切换,一个实际可行的办法是:使用预处理指令。具体实作如下:

#ifndef MYFIRST_HPP
#define MYFIRST_HPP

#if defined(USE_EXPORT)
#define EXPORT export
#else
#define EXPORT
#define

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

#if !defined(USE_EXPORT)
#include "myfirst.cpp"
#endif

#endif

Template与关键词inline

将短小的函数声明为inline,是提高程序执行速度的一个惯用做法。关键词inline用来高速编译器,最好将函数调用替换为被调用函数之实作码,然而编译器不一定进行这一替换动作。
因为将定义式置于头文件,并将该头文件含入多个.c文件内的手法,function templates和inline functions都可以被定义于多个编译单元内。这回产生一种假象,function templates预设情况下就是inline,其实它们不是,如果你希望编写一个inline function template,应该明确的使用关键词inline(除非它本来就被定义域一个class定义式内)。
对于没有写在class定义式内的小型template functions,我们应该将它们声明为inline。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值