C++模板只是伪装成宏的东西吗?
C++模板只是伪装成宏的东西吗?
我已经使用C++编程了几年,经常使用STL,并且多次创建了自己的模板类来了解其实现方式。\n现在我正在尝试将模板更深入地融入我的面向对象设计中,但总是有一个烦人的想法萦绕在我心头:它们只是宏,实际上...如果你真的想的话,你可以使用#define来实现(相当丑陋的)auto_ptrs。\n通过这种方式思考模板有助于我理解我的代码实际上是如何工作的,但我感觉我一定是在某个地方理解错了。宏被认为是邪恶的化身,然而“模板元编程”却很流行。\n那么,真正的区别是什么呢?模板如何避免像#define那样导致的危险,比如\n
- \n
- 在不期望出现问题的地方出现难以理解的编译器错误?
- 代码膨胀?
- 追踪代码困难?
- 设置调试器断点?
\n
\n
\n
\n
C++模板只是伪装成宏吗?
在这里有很多评论试图区分宏和模板。
是的-它们都是相同的东西:代码生成工具。
宏是一种原始形式,没有太多编译器的强制执行(就像在C中使用对象一样-可以做到,但不够美观)。模板更为先进,并且具有更好的编译器类型检查、错误消息等。
然而,每个都有另一方所没有的优势。
模板只能生成动态类类型-宏可以生成几乎任何你想要的代码(除了另一个宏定义)。宏对于将结构化数据的静态表嵌入到代码中非常有用。
另一方面,模板可以实现一些与宏不可能的真正奇怪的事情。例如:
templateclass Unit { double value; public: Unit(double n) { value = n; } Unit operator+(Unit n) { return Unit (value + n.value); } Unit operator-(Unit n) { return Unit (value - n.value); } Unit operator*(double n) { return Unit (value * n); } Unit operator/(double n) { return Unit (value / n); } Unit operator*(Unit n) { return Unit (value * n.value); } Unit operator/(Unit n) { return Unit (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!"); }
模板允许编译器动态创建和使用类型安全的模板实例。编译器实际上在编译时进行模板参数计算,在需要时为每个唯一结果创建单独的类。在条件语句中创建了一个隐含的Unit<1,-1>(距离/时间=速度)类型,但在代码中从未显式声明过。
显然,某个大学的某个人定义了一个具有40多个参数的这种模板(需要一个参考),每个参数代表不同的物理单位类型。想象一下这种类的类型安全性,仅针对您的数字。
我对你的意图有些了解,直到我看到"d+d2, t+t2",那时我就迷失了。你能解释一下它的作用以及与"typedef double Distance"/"typedef double Time"相比的优势吗?后者似乎会产生相同的结果。
声明两个变量:Distance d; Time t; 如果Distance和Time都是double类型,语句(d = t)和表达式(d == t)都是有效的。模板可以防止这种情况发生-为数值提供类型安全性。
啊!谢谢。我永远无法自己推断出来!
"宏对于将结构化数据的静态表嵌入到代码中非常有用"——这不是模板不能做的事情之一。;)模板不能做的事情主要是实体化;创建新的标记等。
模板函数不能做的一个很好的例子是包含调用代码中的行信息的日志函数:#define log(...) someLoggingFunction(__LINE__, __VA_ARGS__)
我对"d+d2, t+t2"还是完全迷失了。你的评论解释了它的作用,但没有解释它是如何工作的。请在你的答案中解释得如此好,以至于连我都能理解!(这设定了一个非常高的门槛)
请把你的示例完整并可运行。你肯定漏掉了什么,因为"d2"和"t2"从未被定义。错误:main.cpp:41:12: error: ‘d2’ was not declared in this scope
。请看这里的示例;请根据你正在尝试展示的内容使其可运行:onlinegdb.com/B12lMGzKI。
我做了一些编辑以使代码可编译,希望能尽快获得批准。这个模板的作用是通过利用不同的模板特化提供维度分析。例如,米的值将具有Unit<1,0>类型,秒为Unit<0,1>,米/秒为Unit<1,-1>等等。这样,您可以确保不同的单位不会相互添加或相互减去(因为它们将具有不兼容的类型),而将这些值相乘或相除将生成具有其适当维度的新类型(就像例子中的Unit<1,0>/Unit<0,1> = Unit<1,-1>)。
C++模板只是伪装成宏吗?
在讨论C++模板和宏的区别之前,让我们先了解一下宏是什么。宏是一种预处理器指令,它在编译器运行之前由预处理器解析。然而,C++模板与宏不同,它们是由编译器解析的。这是MSDN对此的解释。
下面是宏存在的一些问题:
1. 编译器无法验证宏参数是否是兼容的类型。
2. 宏在展开时没有进行特殊的类型检查。
3. 参数i和j被评估两次。如果任何参数有后自增变量,递增操作将被执行两次。
4. 由于宏是由预处理器展开的,编译器的错误消息会引用展开后的宏,而不是宏定义本身。在调试期间,宏将以展开的形式显示出来。
MSDN的链接中使用了一个Min的模板作为“糟糕的例子”。Scott Meyer在他的论文中也提到了关于Min/Max的模板。显然,从技术上讲,你是正确的,但是仅仅说一个是由预处理器处理,另一个是由编译器处理,并不能说明为什么一个比另一个更好。
你太不公平了。作为模板,Min在其不完善的状态下相当容易理解,并且提供了比宏更好的保护。Alexandrescu也提出了解决Min/Max问题的方法,但是对我来说,它太复杂了。
好吧...这就是为什么我引用MSDN的原因。他们非常明确:类型检查、双重递增保护、错误消息。所有这些都来自于它在编译器内部处理的事实,你不能在预处理器中实现。谁在乎模板是否是一个图灵完备的语言呢?
- 是的,Min很容易理解,但也没有用,因为正如SM所提到的那样:“我们在谈论最大函数!为什么这样一个在概念上非常简单的函数会引起如此多的麻烦呢?”
Scott Meyer的所有文章都没有完全说服我。在像C++这样的强类型语言中,如果你想从min()函数中获得引用返回值,那么它的两个参数必须具有相同的类型(和const修饰符),因为根据定义,你在编译时不知道要返回哪个参数的引用。在我看来,他试图解决一个为了论证而人为制造的问题。
十年后,我们现在有了解决min
的推理问题的方法,std::type_identity。template <typename T> T& min(T&, std::type_identity_t<T&>)
这里的前三个问题可以通过使用带有C++ static_assert
的gcc语句表达式或C++ static_assert
来检查传递给宏或gcc/clang语句表达式的所有类型来完全解决。换句话说,在现代C++中,对宏的唯一有效的反对意见是最后一个问题:...编译器错误消息将引用展开后的宏,而不是宏定义本身。在调试期间,宏将以展开的形式显示出来。
C++模板和宏是两种不同的机制。宏是一种文本替换机制,而模板是一种在编译时执行的功能性图灵完备语言,并且与C++类型系统集成。可以将模板看作是语言的一种插件机制。
然而,有人可能会问,C++模板和宏是否只是伪装成宏的东西?这个问题的出现可能是因为模板和宏在某些方面有一些相似之处,比如都可以用于泛型编程。但是,从上述的解释来看,模板和宏是有着本质上的差异的。
为了更好地解答这个问题,我们可以参考一个类比。就像"苹果和橙子一样吗?"的问题一样,一个糟糕的回答是指出苹果和橙子的不同种类和特点,而一个较好的回答是指出苹果和橙子在颜色、果肉密度和果汁的甜度上有着明显的差异。
因此,要回答这个问题,我们需要说明模板和宏的本质区别。模板是一种编译时执行的语言,可以在编译阶段进行类型检查和代码生成,而宏是一种简单的文本替换机制。模板可以根据不同的类型生成不同的代码,而宏只是进行简单的文本替换。此外,模板还可以通过编译器进行错误检查和类型推导,而宏则没有这些功能。
尽管模板和宏在某些方面有一些相似之处,但它们是两种不同的机制。模板是一种功能性语言,可以在编译时执行,并与C++类型系统集成,而宏只是一种简单的文本替换机制。
总之,C++模板和宏是两种不同的机制,它们在功能和实现方式上有着本质上的差异。要正确回答"Are C++ Templates just Macros in disguise?"这个问题,我们需要说明模板和宏的区别,以及模板的独特功能和优势。