C ++模板只是变相的macros?
我已经用C ++进行了几年的编程,而且我已经使用了STL,并且创build了自己的模板类几次,以了解它是如何完成的。
现在我正试图将模板更深入地融入到我的面向对象的devise中,一个唠叨的想法一直回到我身上:它们只是一个macros,真的…您可以使用#define实现(而不是UGLY)auto_ptrs,如果你真的想要。
这种关于模板的思考方式可以帮助我理解代码的实际工作方式,但是我觉得我必须以某种方式忽略这一点。 macros指的是邪恶的化身,但“模板元编程”却风靡一时。
那么,真正的区别是什么? 以及模板如何避免#define引导你进入的危险
- 在你不指望他们的地方出现难以察觉的编译器错误?
- 代码膨胀?
- 跟踪代码有困难吗?
- 设置debugging器断点?
macros是一种文本replace机制。
模板是一个function完整的图灵语言,在编译时被执行,并被集成到C ++types的系统中。 你可以把它们当作语言的插件机制。
它们由编译器parsing,而不是由在编译器之前运行的预处理器parsing。
以下是MSDN关于它的说明: http : //msdn.microsoft.com/zh-cn/library/aa903548(VS.71).aspx
这是macros的一些问题:
- 编译器无法validationmacros参数是否兼容。
- macros没有任何特殊的types检查扩展。
- 我和j参数被评估两次。 例如,如果任一参数具有postincrementedvariables,则会执行两次增量。
- 由于macros由预处理器扩展,编译器错误消息将引用扩展macros,而不是macros定义本身。 此外,macros在debugging过程中将以扩展forms显示。
如果这还不够,我不知道是什么。
这里有很多评论试图区分macros和模板。
是的 – 他们都是一样的东西:代码生成工具。
macros是一种原始的forms,没有太多的编译器强制执行(就像在C中做对象 – 它可以完成,但不是很漂亮)。 模板更先进,并有更好的编译器types检查,错误消息等
然而,每个人都有优势,而另一个则没有。
模板只能生成dynamic类types – macros可以生成几乎所有你想要的代码(除了另一个macros定义)。 macros将结构化数据的静态表embedded到代码中非常有用。
另一方面,模板可以完成一些真正FUNKY的东西,而macros是不可能的。 例如:
template<int d,int t> class Unit { double value; public: Unit(double n) { value = n; } Unit<d,t> operator+(Unit<d,t> n) { return Unit<d,t>(value + n.value); } Unit<d,t> operator-(Unit<d,t> n) { return Unit<d,t>(value - n.value); } Unit<d,t> operator*(double n) { return Unit<d,t>(value * n); } Unit<d,t> operator/(double n) { return Unit<d,t>(value / n); } Unit<d+d2,t+t2> operator*(Unit<d2,t2> n) { return Unit<d+d2,t+t2>(value + n.value); } Unit<d-d2,t-t2> operator/(Unit<d2,t2> n) { return Unit<d-d2,t-t2>(value + n.value); } etc.... }; #define Distance Unit<1,0> #define Time Unit<0,1> #define Second Time(1.0) #define Meter Distance(1.0) void foo() { Distance moved1 = 5 * Meter; Distance moved2 = 10 * Meter; Time time1 = 10 * Second; Time time2 = 20 * Second; if ((moved1 / time1) == (moved2 / time2)) printf("Same speed!"); }
该模板允许编译器dynamic地创build和使用模板的types安全实例。 编译器实际上在编译时做模板参数math运算,在每个唯一结果需要的地方创build单独的类。 有一个隐含的Unit <1,-1>(distance / time = velocity)types,在条件内创build和比较,但从未在代码中明确声明。
显然,大学里有人用40多个参数定义了一个这样的模板(需要参考),每个参数代表不同的物理单位types。 想想这种types的types安全性,只是为了你的数字。
答案是这么长,我不能总结一切,但:
- 例如,macros在函数模板中并不确保types安全性:编译器无法validationmacros参数是否兼容types – 也是在函数模板实例化时,编译器知道
int
或float
定义operator +
- 模板为元编程打开了大门(简而言之,在编译时评估事物并做出决定):在编译时可以知道types是整数还是浮点数; 无论是一个指针或是否是const限定的等等… 在即将到来的c ++ 0x中看到“type traits”
- 类模板有部分专业化
- 函数模板有明确的完全专业化,在你的例子中
add<float>(5, 3);
可以以不同的方式add<int>(5, 3);
这是macros不可能的 - macros没有任何的范围
-
#define min(i, j) (((i) < (j)) ? (i) : (j))
– 对i
和j
参数进行两次评估。 例如,如果任一参数具有postincrementedvariables,则会执行两次增量 - 由于macros由预处理器扩展,编译器错误消息将引用扩展macros,而不是macros定义本身。 此外,macros在debugging过程中将以扩展forms显示
- 等等…
注意:在极less数情况下,我更喜欢依赖于可变macros,因为在c ++ 0x成为主stream之前,不存在可变参数模板。 C ++ 11是活的。
参考文献:
- C ++ FAQ Lite:[35]模板
- 模板的优点
- 模板与macros(C ++)
在一个非常基本的层面上,是的,模板只是macros观替代。 但是通过这样思考,你正在逃避很多事情。
考虑模板专门化,据我所知,你不能用macros来模拟。 这不仅对于某些types允许特殊实现,而且是模板元编程中的关键部分之一:
template <typename T> struct is_void { static const bool value = false; } template <> struct is_void<void> { static const bool value = true; }
这本身就是你可以做很多事情的一个例子。 模板本身是图灵完整的。
这忽略了一些非常基本的东西,比如范围,types安全以及这个macros观更加混乱。
NO 。 一个简单的计数器例子:模板遵守命名空间,macros的忽略命名空间(因为它们是预处理器语句)。
namespace foo { template <class NumberType> NumberType add(NumberType a, NumberType b) { return a+b; } #define ADD(x, y) ((x)+(y)) } // namespace foo namespace logspace { // no problemo template <class NumberType> NumberType add(NumberType a, NumberType b) { return log(a)+log(b); } // redefintion: warning/error/bugs! #define ADD(x, y) (log(x)+log(y)) } // namespace logspace
C ++模板有点像Lispmacros(不是Cmacros),因为它们是在已经parsing过的代码版本上运行的,它们让你在编译时生成任意代码。 不幸的是,你正在编程类似于原始Lambda演算的东西,所以像循环这样的高级技术是很麻烦的。 有关所有的细节,请参阅Krysztof Czarnecki和Ulrich Eisenecker的Generative Programming 。
如果你正在寻找一个更深入的主题的治疗,我可以把你变成每个人最喜欢的C ++ hater 。 这个人知道并讨厌比我所能想象的更多的C ++。 这同时使FQA令人难以置信的煽动性和优秀的资源。
- 模板是types安全的。
- 模板对象/types可以被命名空间,成为一个类的私有成员等
- 模板化函数的参数不会在整个函数体中复制。
这真的是一个大问题,并防止大量的错误。
没有提到的是模板函数可以推导出参数types。
模板<typename T> void func(T t) { T make_another = t;
有人可能会争辩说,即将到来的“typeof”操作符可以解决这个问题,但即使它不能拆分其他模板:
模板<typename T> void func(container <T> c)
甚至:
template <tempate <typename> class Container,typename T> void func(Container <T> ct)
我也觉得专业化的课题还不够。 以下是macros不能做的一个简单例子:
模板<typename T> T min(T a,TB) { 返回一个<b? a:b; } 模板<> char * min(char * a,char * b) { 如果(strcmp(a,b)<0) 返回一个; 其他 回报b; }
这个空间太小,不能进入专业化types,但就我而言,你可以用它做什么,这是令人兴奋的。
不,这是不可能的。 预处理程序对于像T的容器这样的东西(仅仅)是不够的,但是对于模板可以完成的其他事情来说,它是不够的。
对于一些实例,请阅读Andre Alexandrescu的“ Modern C ++ Programming”或Dave Abrahams和Aleksey Gurtovoy的“ C ++ Metaprogramming” 。 在预处理器中,几乎没有任何一本书可以模拟到极其微小的程度。
编辑:至于typename
去,要求是非常简单的。 编译器不能总是找出一个从属名称是否指向一个types。 使用typename
显式告诉编译器它引用了一个types。
struct X { int x; }; struct Y { typedef long x; }; template <class T> class Z { T::x; }; Z<X>; // T::x == the int variable named x Z<Y>; // T::x == a typedef for the type 'long'
typename
告诉编译器,一个特定的名字是指一个types,而不是一个variables/值,所以(例如)你可以定义这种types的其他variables。
这个答案意在说明C预处理器以及它如何用于generics编程
他们在某种程度上是因为它们使一些类似的语义。 C预处理器已被用来启用通用数据结构和algorithm(请参阅令牌Concatination )。 但是,如果不考虑C ++模板的其他function,它将使整个通用编程游戏成为一个“ 清理者”来阅读和实现。
如果任何人想看到硬核C只有通用编程在行动阅读libevent源代码 – 这也在这里提到。 容器/algorithm的大量集合被实现,并且在单头文件中完成(非常可读)。 我真的很佩服这个,C ++模板代码(我更喜欢它的其他属性)非常详细。
模板是types安全的。 使用定义,您可以编译代码,但仍然无法正常工作。
编译器获取代码之前,macros展开。 这意味着你会得到一个扩展的代码的错误信息,而debugging器只能看到扩展的版本。
使用macros,总会有一些expression式被评估两次的机会。 想象一下,传递一些像++ x这样的参数。
模板可以放在命名空间中,也可以是类的成员。 macros只是一个预处理步骤。 基本上,模板是语言的第一类成员,与其他所有东西都打得好(更好?)。
模板可以做比macros预处理器能够做的更多。
例如,有模板专门化: 如果这个模板是这个types或常量的实例,比不使用默认的实现,但这里这一个…
…模板可以强制一些参数是相同的types,等等…
以下是您可能想要查看的一些来源:
- Vandervoorde和Jossutis的C ++模板 。 这是关于模板我知道的最好和最完整的书。
- boost库几乎完全由模板定义组成。
虽然模板参数是经过types检查的,并且模板比macros有许多优点,但是模板非常像macros,因为它们仍然基于文本replace。 编译器不会validation您的模板代码是否有意义,直到您给它的types参数替代。 例如,Visual C ++不会抱怨这个函数,只要你不实际调用它:
template<class T> void Garbage(int a, int b) { fdsa uiofew & (a9 s) fdsahj += *! wtf; }
因此,一般来说,不可能知道模板代码是否能够正确地工作或编译成功,对于模板旨在接受的types参数的给定类别。
在我看来,macros是C的一个坏习惯。虽然它们对于一些可能是有用的,但是当有typedef和模板时,我并不认为它们是真正的需要。 模板是面向对象编程的自然延续。 你可以做更多的模板…
考虑这个…
int main() { SimpleList<short> lstA; //... SimpleList<int> lstB = lstA; //would normally give an error after trying to compile }
为了进行转换,你可以使用一些被称为转换构造函数和序列构造函数的东西(看看结尾)
#include <algorithm> template<class T> class SimpleList { public: typedef T value_type; typedef std::size_t size_type; private: struct Knot { value_type val_; Knot * next_; Knot(const value_type &val) :val_(val), next_(0) {} }; Knot * head_; size_type nelems_; public: //Default constructor SimpleList() throw() :head_(0), nelems_(0) {} bool empty() const throw() { return size() == 0; } size_type size() const throw() { return nelems_; } private: Knot * last() throw() //could be done better { if(empty()) return 0; Knot *p = head_; while (p->next_) p = p->next_; return p; } public: void push_back(const value_type & val) { Knot *p = last(); if(!p) head_ = new Knot(val); else p->next_ = new Knot(val); ++nelems_; } void clear() throw() { while(head_) { Knot *p = head_->next_; delete head_; head_ = p; } nelems_ = 0; } //Destructor: ~SimpleList() throw() { clear(); } //Iterators: class iterator { Knot * cur_; public: iterator(Knot *p) throw() :cur_(p) {} bool operator==(const iterator & iter)const throw() { return cur_ == iter.cur_; } bool operator!=(const iterator & iter)const throw() { return !(*this == iter); } iterator & operator++() { cur_ = cur_->next_; return *this; } iterator operator++(int) { iterator temp(*this); operator++(); return temp; } value_type & operator*()throw() { return cur_->val_; } value_type operator*() const { return cur_->val_; } value_type operator->() { return cur_->val_; } const value_type operator->() const { return cur_->val_; } }; iterator begin() throw() { return iterator(head_); } iterator begin() const throw() { return iterator(head_); } iterator end() throw() { return iterator(0); } iterator end() const throw() { return iterator(0); } //Copy constructor: SimpleList(const SimpleList & lst) :head_(0), nelems_(0) { for(iterator i = lst.begin(); i != lst.end(); ++i) push_back(*i); } void swap(SimpleList & lst) throw() { std::swap(head_, lst.head_); std::swap(nelems_, lst.nelems_); } SimpleList & operator=(const SimpleList & lst) { SimpleList(lst).swap(*this); return *this; } //Conversion constructor template<class U> SimpleList(const SimpleList<U> &lst) :head_(0), nelems_(0) { for(typename SimpleList<U>::iterator iter = lst.begin(); iter != lst.end(); ++iter) push_back(*iter); } template<class U> SimpleList & operator=(const SimpleList<U> &lst) { SimpleList(lst).swap(*this); return *this; } //Sequence constructor: template<class Iter> SimpleList(Iter first, Iter last) :head_(0), nelems_(0) { for(;first!=last; ++first) push_back(*first); } };
在模板上看看来自cplusplus.com的信息 ! 你可以使用模板来做所谓的traits,它使用了一些types的文档。 你可以用模板做更多的事情,然后用macros来做什么呢!
呈现typename关键字以启用上下文无关嵌套的typdef。 这些是特性技术所需要的,它允许将元数据添加到types(特别是诸如指针的内置types)中,这是编写STL所必需的。 typename关键字与class关键字相同。
我们来试一下原始的例子。 考虑
#define min(a,b) ((a)<(b))?(a):(b)
被调用为
c = min(a++,++b);
当然,真正的区别是更深的,但这应该足以抛弃与macros的相似之处。
编辑 :不,你不能确保macros的types安全。 你将如何实现types安全min()
为每个types定义less于比较(即操作operrator<
)?
模板理解数据types。 macros不。
这意味着你可以做如下的东西…
- 定义可以接受任何数据types的操作(例如,用于包装数字的操作 ),然后根据数据types是整数还是浮点数,提供专门的select适当的algorithm
- 在编译时确定你的数据types的各个方面,允许微软使用它的C ++重载的strcpy_s及其stream派
另外,因为模板是types安全的,所以有一些模板编码技术可以想象用一些假设的高级预处理器来执行,但是最好是模糊和易出错的(例如, 模板模板参数 ,默认模板参数,策略模板为在现代C ++devise中讨论)。
模板与其最基本的function类似。 毕竟,模板被引入到语言中,成为macros的“文明”select。 但是,即使涉及到最基本的function,相似性也只是皮肤很深。
但是,一旦我们获得更高级的模板特性,如特殊化(部分或者显式),与macros的任何明显的相似性都会完全消失。
这不是答案已经说明的结果。
与那些需要编程的科学家,外科医生,graphics艺术家和其他人一起工作 – 但不是也不会是专业的全职软件开发人员 – 我发现偶尔的程序员很容易理解macros,而模板似乎需要更高的抽象思维的水平只有在C ++中深入和持续的编程经验才有可能。 它需要处理模板是有用的概念的代码的许多实例,使概念充分地使用。 虽然可以说任何语言function,模板的经验量比专业休闲程序员可能从他们的日常工作中获得的差距更大。
一般的天文学家或电子工程师可能会把macros指望得很好,甚至可以理解为什么应该避免使用macros,但是对于日常使用来说,模板不够好。 在这种情况下,macros实际上更好。 当然,有很多例外, 一些物理学家在专业软件工程师的周围运行,但这不是典型的。
macros有一些基本的问题。
首先,他们不尊重范围或types。 如果我有#define max(a, b)...
,那么无论什么原因,只要我的程序中有令牌max
,它将被replace。 它将被replace,如果它是一个variables名称或深层嵌套范围内。 这可能会导致难以发现的编译错误。 相反,模板在C ++types的系统中工作。 模板函数可以在一个作用域内重用它的名字,并且不会尝试重写一个variables名。
其次,macros不能变化。 模板std::swap
通常只是声明一个临时variables,并做明显的分配,因为这是正常工作的显而易见的方式。 这是macros观将被限制。 对于大型vector来说这将是非常低效的,所以vector具有特殊的swap
,交换引用而不是整个内容。 (这对于一般的C ++程序员不应该编写而是使用的东西来说非常重要)。
第三,macros不能做任何forms的types推断。 你不能写一个通用的交换macros,因为它必须声明一个types的variables,它不知道types是什么。 模板是types意识的。
模板的力量的一个很好的例子是最初被称为标准模板库的标准模板库,它在标准中被称为容器,algorithm和迭代器。 看看他们是如何工作的,并试着想怎样用macros替代它。 亚历山大·斯捷潘诺夫(Alexander Stepanov)研究了大量的语言来实现他的STL思想,并得出结论:带模板的C ++是唯一可用的。
模板提供某种程度的types安全性。
模板集成在语言中,是types安全的。
告诉我你将如何使用macros来做到这一点。 这是沉重的模板元编程。
https://www.youtube.com/watch?v=0A9pYr8wevk
我认为macrosAFAIK不能像模板部分特化可以完成的那样计算types。