"typename"和"class"背后隐藏着什么?你必须知道的区别
\n\n如果你曾经用C++编写过模板,那么你一定遇到过两个看似可以互换的关键字:typename和class。这看起来像是一个小细节,但在Stack Overflow上,这个问题已经积累了数千次浏览。为什么?因为在看似简单的背后,隐藏着一个可能破坏你代码编译的陷阱。
在本文中,我们将一劳永逸地弄清楚:C++模板中typename和class有什么区别,何时可以将它们用作同义词,以及何时会导致错误。没有废话——只有代码、示例和明确的规则。
\n\n简短回答:到底有没有区别?
\n\nStack Overflow上最流行的回答是:对于声明模板参数来说——没有区别。你可以这样写:
\n\ntemplate <typename T> void foo(T t);\n或者
\ntemplate <class T> void foo(T t);\n\n两种写法完全合法,并且做的是同一件事:声明模板参数T。从历史上看,class首先出现(早在C++98中),而typename后来才添加,以强调参数不仅可以是一个类,还可以是任何类型(int、double、指针等)。
然而,魔鬼藏在细节中。有一种场景下,typename是必需的,而class则行不通。这就是——依赖名称(dependent names)。
主要陷阱:依赖类型(dependent types)
\n\n想象一下这种情况:你正在编写一个模板,在其中访问嵌套在模板参数中的类型。例如,你有一个容器,你想获取它的迭代器:
\n\ntemplate <typename Container>\nvoid printFirst(const Container& c) {\n Container::const_iterator it = c.begin(); // 错误!\n std::cout << *it;\n}\n\n为什么这不能编译?因为Container::const_iterator是一个依赖名称。编译器在解析阶段不知道Container是什么。它可能是一个类,也可能是一个静态变量的名称?默认情况下,C++认为Container::const_iterator是一个变量(或静态数据成员)的名称,而不是类型。只有关键字typename告诉编译器:"嘿,这是一个类型!"。
修正后:
\n\ntemplate <typename Container>\nvoid printFirst(const Container& c) {\n typename Container::const_iterator it = c.begin(); // 正确!\n std::cout << *it;\n}\n\n重要提示:这里class行不通。写class Container::const_iterator是语法错误。typename是唯一正确的选择。
何时使用typename而不是class?
\n\n记住一个简单的规则:typename用于两种情况:
- \n
- 作为模板参数(与
class同义):template <typename T>\n - 用于指示依赖名称是一个类型:
typename T::value_type\n
而class仅用于第一种情况。而且,许多C++程序员更倾向于在参数中使用typename,以避免与类声明混淆。
带解释的完整示例
\n\n让我们分析一个实际代码,它演示了C++模板中typename和class的区别:
\n\n#include <iostream>\n#include <vector>\n#include <list>\n\n// 步骤1:使用class的模板(或typename——没有区别)\ntemplate <class T>\nclass MyContainer {\npublic:\n // 步骤2:对依赖类型使用typename\n // T::value_type 是一个类型,而不是