引用C++标准库:教程和手册:
目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。
这是为什么?
(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)
引用C++标准库:教程和手册:
目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。
这是为什么?
(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)
当前回答
我必须写一个模板类,这个例子对我很有用
下面是一个动态数组类的示例。
#ifndef dynarray_h
#define dynarray_h
#include <iostream>
template <class T>
class DynArray{
int capacity_;
int size_;
T* data;
public:
explicit DynArray(int size = 0, int capacity=2);
DynArray(const DynArray& d1);
~DynArray();
T& operator[]( const int index);
void operator=(const DynArray<T>& d1);
int size();
int capacity();
void clear();
void push_back(int n);
void pop_back();
T& at(const int n);
T& back();
T& front();
};
#include "dynarray.template" // this is how you get the header file
#endif
现在,在.template文件中,您可以像往常一样定义函数。
template <class T>
DynArray<T>::DynArray(int size, int capacity){
if (capacity >= size){
this->size_ = size;
this->capacity_ = capacity;
data = new T[capacity];
}
// for (int i = 0; i < size; ++i) {
// data[i] = 0;
// }
}
template <class T>
DynArray<T>::DynArray(const DynArray& d1){
//clear();
//delete [] data;
std::cout << "copy" << std::endl;
this->size_ = d1.size_;
this->capacity_ = d1.capacity_;
data = new T[capacity()];
for(int i = 0; i < size(); ++i){
data[i] = d1.data[i];
}
}
template <class T>
DynArray<T>::~DynArray(){
delete [] data;
}
template <class T>
T& DynArray<T>::operator[]( const int index){
return at(index);
}
template <class T>
void DynArray<T>::operator=(const DynArray<T>& d1){
if (this->size() > 0) {
clear();
}
std::cout << "assign" << std::endl;
this->size_ = d1.size_;
this->capacity_ = d1.capacity_;
data = new T[capacity()];
for(int i = 0; i < size(); ++i){
data[i] = d1.data[i];
}
//delete [] d1.data;
}
template <class T>
int DynArray<T>::size(){
return size_;
}
template <class T>
int DynArray<T>::capacity(){
return capacity_;
}
template <class T>
void DynArray<T>::clear(){
for( int i = 0; i < size(); ++i){
data[i] = 0;
}
size_ = 0;
capacity_ = 2;
}
template <class T>
void DynArray<T>::push_back(int n){
if (size() >= capacity()) {
std::cout << "grow" << std::endl;
//redo the array
T* copy = new T[capacity_ + 40];
for (int i = 0; i < size(); ++i) {
copy[i] = data[i];
}
delete [] data;
data = new T[ capacity_ * 2];
for (int i = 0; i < capacity() * 2; ++i) {
data[i] = copy[i];
}
delete [] copy;
capacity_ *= 2;
}
data[size()] = n;
++size_;
}
template <class T>
void DynArray<T>::pop_back(){
data[size()-1] = 0;
--size_;
}
template <class T>
T& DynArray<T>::at(const int n){
if (n >= size()) {
throw std::runtime_error("invalid index");
}
return data[n];
}
template <class T>
T& DynArray<T>::back(){
if (size() == 0) {
throw std::runtime_error("vector is empty");
}
return data[size()-1];
}
template <class T>
T& DynArray<T>::front(){
if (size() == 0) {
throw std::runtime_error("vector is empty");
}
return data[0];
}
其他回答
我必须写一个模板类,这个例子对我很有用
下面是一个动态数组类的示例。
#ifndef dynarray_h
#define dynarray_h
#include <iostream>
template <class T>
class DynArray{
int capacity_;
int size_;
T* data;
public:
explicit DynArray(int size = 0, int capacity=2);
DynArray(const DynArray& d1);
~DynArray();
T& operator[]( const int index);
void operator=(const DynArray<T>& d1);
int size();
int capacity();
void clear();
void push_back(int n);
void pop_back();
T& at(const int n);
T& back();
T& front();
};
#include "dynarray.template" // this is how you get the header file
#endif
现在,在.template文件中,您可以像往常一样定义函数。
template <class T>
DynArray<T>::DynArray(int size, int capacity){
if (capacity >= size){
this->size_ = size;
this->capacity_ = capacity;
data = new T[capacity];
}
// for (int i = 0; i < size; ++i) {
// data[i] = 0;
// }
}
template <class T>
DynArray<T>::DynArray(const DynArray& d1){
//clear();
//delete [] data;
std::cout << "copy" << std::endl;
this->size_ = d1.size_;
this->capacity_ = d1.capacity_;
data = new T[capacity()];
for(int i = 0; i < size(); ++i){
data[i] = d1.data[i];
}
}
template <class T>
DynArray<T>::~DynArray(){
delete [] data;
}
template <class T>
T& DynArray<T>::operator[]( const int index){
return at(index);
}
template <class T>
void DynArray<T>::operator=(const DynArray<T>& d1){
if (this->size() > 0) {
clear();
}
std::cout << "assign" << std::endl;
this->size_ = d1.size_;
this->capacity_ = d1.capacity_;
data = new T[capacity()];
for(int i = 0; i < size(); ++i){
data[i] = d1.data[i];
}
//delete [] d1.data;
}
template <class T>
int DynArray<T>::size(){
return size_;
}
template <class T>
int DynArray<T>::capacity(){
return capacity_;
}
template <class T>
void DynArray<T>::clear(){
for( int i = 0; i < size(); ++i){
data[i] = 0;
}
size_ = 0;
capacity_ = 2;
}
template <class T>
void DynArray<T>::push_back(int n){
if (size() >= capacity()) {
std::cout << "grow" << std::endl;
//redo the array
T* copy = new T[capacity_ + 40];
for (int i = 0; i < size(); ++i) {
copy[i] = data[i];
}
delete [] data;
data = new T[ capacity_ * 2];
for (int i = 0; i < capacity() * 2; ++i) {
data[i] = copy[i];
}
delete [] copy;
capacity_ *= 2;
}
data[size()] = n;
++size_;
}
template <class T>
void DynArray<T>::pop_back(){
data[size()-1] = 0;
--size_;
}
template <class T>
T& DynArray<T>::at(const int n){
if (n >= size()) {
throw std::runtime_error("invalid index");
}
return data[n];
}
template <class T>
T& DynArray<T>::back(){
if (size() == 0) {
throw std::runtime_error("vector is empty");
}
return data[size()-1];
}
template <class T>
T& DynArray<T>::front(){
if (size() == 0) {
throw std::runtime_error("vector is empty");
}
return data[0];
}
当您在编译步骤中使用模板时,编译器将为每个模板实例化生成代码。在编译和链接过程中,.cpp文件被转换为纯对象或机器代码,其中包含引用或未定义的符号,因为main.cpp中包含的.h文件没有实现YET。这些文件已准备好与另一个对象文件链接,该对象文件定义了模板的实现,因此您拥有完整的a.out可执行文件。
然而,由于模板需要在编译步骤中进行处理,以便为您定义的每个模板实例化生成代码,因此简单地编译一个与它的头文件分离的模板是行不通的,因为它们总是并行的,因为每个模板实例化都是一个全新的类。在常规类中,您可以将.h和.cpp分开,因为.h是该类的蓝图,.cpp是原始实现,因此任何实现文件都可以定期编译和链接,但是使用模板.h是类外观的蓝图,而不是对象外观,这意味着模板.cpp文件不是类的原始常规实现,它只是一个类的蓝图,因此任何.h模板文件的实现都无法编译,因为您需要一些具体的东西来编译,在这个意义上,模板是抽象的。
因此,模板永远不会单独编译,只在其他源文件中有具体实例化的地方编译。然而,具体的实例化需要知道模板文件的实现,因为简单地使用.h文件中的具体类型修改类型名T并不能完成任务,因为.cpp要链接什么,我以后找不到它,因为记住模板是抽象的,无法编译,所以我现在不得不给出实现,这样我就知道要编译和链接什么,现在我有了实现,它就链接到了封闭的源文件中。基本上,当我实例化一个模板时,我需要创建一个全新的类,如果我不知道该类在使用我提供的类型时应该是什么样子,我就不能这样做,除非我通知编译器模板实现,所以现在编译器可以用我的类型替换t,并创建一个可以编译和链接的具体类。
总之,模板是类外观的蓝图,类是对象外观的蓝图。我不能将模板与它们的具体实例化分开编译,因为编译器只编译具体类型,换句话说,至少在C++中,模板是纯语言抽象的。可以说,我们必须对模板进行去抽象,我们通过给它们一个具体的类型来处理,这样我们的模板抽象就可以转换为一个常规的类文件,进而可以正常编译。分隔template.h文件和template.cpp文件是没有意义的。这是没有意义的,因为.cpp和.h的分离仅限于.cpp可以单独编译和单独链接的地方,因为我们不能单独编译它们,因为模板是一个抽象,因此,我们总是被迫将抽象与具体实例化放在一起,其中具体实例化总是必须知道所使用的类型。
这意味着typename T get在编译步骤而不是链接步骤中被替换,所以如果我试图编译一个模板而不将T替换为一个对编译器来说完全没有意义的具体值类型,结果无法创建对象代码,因为它不知道T是什么。
从技术上讲,可以创建某种功能来保存template.cpp文件,并在其他源中找到它们时切换类型。
附带说明一下,当为模板类进行专门化时,可以将头与实现分开,因为根据定义,专门化意味着我专门化了一个可以单独编译和链接的具体类型。
我建议看看这个gcc页面,它讨论了模板实例化的“cfront”和“borland”模型之间的权衡。
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html
“borland”模型符合作者的建议,提供完整的模板定义,并多次编译。
它包含关于使用手动和自动模板实例化的明确建议。例如,“-repo”选项可用于收集需要实例化的模板。或者另一个选项是使用“-fno隐式模板”禁用自动模板实例化,以强制手动模板实例化。
根据我的经验,我依赖于为每个编译单元实例化的C++标准库和Boost模板(使用模板库)。对于我的大型模板类,我为所需的类型进行了一次手动模板实例化。
这是我的方法,因为我提供的是一个工作程序,而不是用于其他程序的模板库。这本书的作者Josuttis在模板库方面做了大量工作。
如果我真的担心速度,我想我会探索使用预编译头https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
这在许多编译器中得到了支持。然而,我认为使用模板头文件时,预编译头文件会很困难。
在头文件中编写声明和定义是一个好主意的另一个原因是为了可读性。假设Utility.h中有这样一个模板函数:
template <class T>
T min(T const& one, T const& theOther);
在Utility.cpp中:
#include "Utility.h"
template <class T>
T min(T const& one, T const& other)
{
return one < other ? one : other;
}
这需要这里的每个T类实现小于运算符(<)。当您比较两个尚未实现“<”的类实例时,它将抛出编译器错误。
因此,如果将模板声明和定义分开,则无法仅读取头文件来查看此模板的输入和输出,以便在自己的类上使用此API,尽管在这种情况下编译器会告诉您需要重写哪个运算符。
只是在这里添加一些值得注意的内容。当模板类的方法不是函数模板时,可以在实现文件中定义它们。
myQueue.hpp:
template <class T>
class QueueA {
int size;
...
public:
template <class T> T dequeue() {
// implementation here
}
bool isEmpty();
...
}
myQueue.cpp:
// implementation of regular methods goes like this:
template <class T> bool QueueA<T>::isEmpty() {
return this->size == 0;
}
main()
{
QueueA<char> Q;
...
}