函数模块是怎么链接的?
在下面几个文件中
// func.hpp
template<typename T>
T const& func(T const &v);
// func.cpp
template<typename T>
T const& func(T const &v)
{
return v;
}
当编译时会产生error
undefined reference to `int const& func<int>(int const&)'
编译器在编译func.cpp
时,只是读到了func
函数模板的实现,并没有读到任何需要生成函数模板实例的语句,所以并不会生成任何func
函数的实例
而在编译main.cpp
时,虽然用到了一个函数模板实例,但因为main.cpp
只是将func.hpp
头文件包含进来,而后者只是一个func
函数模板的声明,并无具体函数实现,此时编译器也无法生成func
函数模块实例,只好保留一个调用链接,期望在最后的链接过程中可以找到函数实现,但是实现不存在,因此链接出错
此时可以使用明确生成函数模板
// func.cpp
template<typename T>
T const& func(T const &v)
{
return v;
}
template int const& func(int const &v); // 明确生成模板实例 指示编译器根据此函数声明寻找合适的模板实现
当关键字template
后没有模板参数列表,而是一个函数声明时,意味着指示编译器根据此函数声明寻找合适的模板实现
此时将函数声明为
T=int
,但是这只是权宜之计因此,模板的实现也应该放在头文件内,此时在其他代码文件中可以直接将模板的实现也包含进来,当需要生成模板实例时,编译器可根据已知模板实现当场生成,从而无需依赖在别的目标文件中生成的模板实例
Export Template
C++98
提供了另一种组织模板代码的方式–Export Template
,外名模板(将定义和实现放在同一头文件中的模板可称为内名模板)
// func.hpp
export template<typename T>
T const& func(T const &v);
// func.cpp
export template<typename T>
T const& func(T const &v)
{
return v;
}
目前公开公布的支持外名模板的
C++
编译器有Comeau C++
和Borland C++
类模板的一些用法
前置类模板生成
template<typename T> class my_class; // 前置类模板生成
在类模板内实现的成员函数模板
template<typename T=int>
class my_class
{
// 在类模板内实现的成员函数模板
void push(T const &v)
{
std::cout << "push" << std::endl;
}
}
成员函数声明,将在类模板外实现
template<typename T=int>
class my_class
{
// 成员函数声明,将在类模板外实现
void pop();
};
在类模板外实现的成员函数模板
template<typename T>
void my_class<T>::pop()
{
std::cout << "pop" << std::endl;
}
成员函数模板:普通类的成员函数模板可以在类中当场实现,也可以在类外单独实现
struct normal_class
{
template<typename T>
void set(T const &v) {
value = int(v);}
template<typename T>
T get();
};
template<typename T>
T normal_class::get()
{
return T(value);
}
类模板的成员函数还可以有额外的模板参数
template<typename T0>
struct normal_class
{
template<typename T1>
T1 get();
};
template<typename T0> template<typename T1>
T1 normal_class<T0>::get()
{
return T1(value);
}
模板参数类型
一般情况下,模板参数都是用
typename
和class
标记,并且在函数及类模板的实现中,模板参数是用于指代具体类型,因此,这两个关键字标记类型的模板参数被称为类型模板参数,除此之外,还有以下四种类型
- 整数及枚举型
- 指向对象或函数的指针
- 对对象函数的引用
- 指向对象成员的指针
以上四种参数统称为非类型模板参数,其在模板参数列表中的声明方式与对应类型的变量声明一致,此外模板参数还可以是一个模板,称为模板模板参数
非类型模板参数的作用相当于为函数模板或类模板预定义一些常量,在生成模板实例时,也要求必须以常量即编译期已知的值为非类型模板参数赋值
整数模板参数
当需要为同一算法或类定义不同变量时,最适合用非类型模板参数实现,这样既可以免去因常量不同而导致重写代码,又免去将已知常量当作变量实现所带来的额外运行开销
template<typename T, unsigned size>
class ay
{
T elems[size];
public:
T& operator[](unsigned i)
{
return elems[i];
}
};
ay<char, 2> ap{
}; // 调用
指向对象或函数的指针
在变与不变之间取得最优实现,通常函数指针的作用是回调callback
template<typename T, void (*f)(T &v)>
void foreach(T array[], unsigned size)
{
for (unsigned i = 0; i < size; i ++)
{
f(array[i]);
}
}
template<typename T>
void inc(T &v)
{
++ v;
}
template<typename T>
void dec(T &v)
{
-- v;
}
template<typename T>
void print(T &v)
{
std::cout << v << ' ';
}
int arr[] = {
1, 2, 3, 4, 5
};
foreach<int, print<int>> (arr, 5);
foreach<int, inc<int>> (arr, 5);
foreach<int, print<int>> (arr, 5);
指针及引用模板参数
略
成员函数指针模板参数
以成员函数指针作为模板参数时,其用法与函数指针型模板参数相似
int (some_class::* mfp)(int);
class some_value
{
private:
int value;
public:
explicit some_value(int _value): value(_value){
}
int add_by(int op){
return value += op;
}
int sub_by(int op){
return value -= op;
}
};
除了mfp
之外,其他部分都用于描述所指函数的各个细节,当需要多处定义同类型成员函数指针时,可以先用typedef
为所定义指针类型起个别名,此时some_class_mfp
是一个别名
typedef int (some_class:: *some_class_mfp)(int);
some_class_mfp mfp;
template<some_value_mfp func>
int call(some_value &va, int op)
{
return (va.*func)(op);
}
此处&some_value::add_by
即some_class_mfp
别名,此时some_class_mfp
是一个类型,与指向对象或函数的指针类似
some_value v0(0);
cout << call<&some_value::add_by>(v0, 1) << endl;
元编程技巧
为各种基本类型操作提供可重用代码支持,是C++
元编程的一个重要课题
类型操作中最基本的需求是根据若干类型推导出另一类型(如给定一个容器类型,得到其迭代器类型;或者给定一个迭代器类型,求出其标签类型)
在
C++
中可由若干编译期已知的元素推导出其他编译期已知元素的机制都可称为元函数
模板即元函数体,模板参数即元函数参数,模板中的嵌套定义即元函数的返回值,当改模板有特例时,相当于在元函数中实现一种条件判断逻辑
#if 模板实参匹配模板特例参数
@echo 特例中的类型
#else if
@echo 通例中的类型
通例
template<typename T0, typename T1>
struct is_same
{
enum {
result = 0
};
};
template<bool cond, typename Type_True, typename Type_false>
struct if_
{
typedef Type_True return_type;
};
特例
template<typename T>
struct is_same<T, T>
{
enum {
result = 1
};
};
template<typename Type_True, typename Type_false>
struct if_<false, Type_True, Type_false>
{
typedef Type_True return_type;
};
is_same
用于判断两类型是否相同,由于其返回值需要具有布尔值语义,元函数if_
实现依条件返回两种类型之一
template<typename MV1, typename MV2, typename tag1, typename tag2>
struct return_type_of
{
typedef typename if_<is_same<tag1, matrix_tag>::result && is_same<tag2, vector_tag>::result,
MV2, MV2>::return_type return_type;
};
typename
的作用是显式调用以促使编译器“执行”该元函数,否则将导致逻辑不正确