有没有特别好的理由选择使用详细的类型说明符?例如,在某些情况下,需要使用template
ortypename
关键字来消除依赖项template
或类型的歧义。
但我想不出任何例子,比如枚举。以下面的代码示例为例:
enum Foo { A, B };
void bar(Foo foo);
void baz(enum Foo foo);
为什么我会选择使用baz()
提供的语法bar()
(反之亦然)?有什么模棱两可的情况吗?
有没有特别好的理由选择使用详细的类型说明符?例如,在某些情况下,需要使用template
ortypename
关键字来消除依赖项template
或类型的歧义。
但我想不出任何例子,比如枚举。以下面的代码示例为例:
enum Foo { A, B };
void bar(Foo foo);
void baz(enum Foo foo);
为什么我会选择使用baz()
提供的语法bar()
(反之亦然)?有什么模棱两可的情况吗?
没有理由使用此类说明符,除非您正在处理名称被不同“种类”的名称隐藏的情况。例如,声明一个以Foo
枚举声明命名的变量是完全合法的,因为非正式地说,对象名和类型名存在于独立的“命名空间”中(更正式的规范请参见 3.3/4)
enum Foo { A, B };
int Foo;
声明后int Foo
,您的bar
声明将失效,而更详细的baz
声明将保持有效。
声明用户定义的类型需要详细的类型说明符。一种用例是转发声明您的类型。万一您有一个与您在范围内可见的函数同名的函数,enum
您可能需要在函数声明中使用详细的类型说明符:
enum A { A_START = 0 };
void A(enum A a) {}
int main() {
enum A a;
A( a );
}
选择使用详细类型说明符的一个很好的理由是在头文件中转发声明结构或类。给定一个定义类型的标头
// a.h
struct foo {};
您可以包含该标题来原型您的功能
#include "a.h"
void foo(foo * ) ;
或使用详细类型:
void foo(struct foo * ) ;
或者,使用详细类型明确转发声明:
struct foo ;
void foo( foo * ) ;
最后两种方法中的任何一种都可以帮助避免您的标题逐渐退化为一个完全连接的网络,其中包括任何单个标题都会拉动所有其余部分(我正在开发一个软件产品,可悲的是,这会迫使您在多次您可以想象的各种变化在逻辑上都是孤立的)。
我知道 C++11 也将允许对枚举进行这种前向引用,这是 C++01 编译器目前不允许的。
可能出现的一个示例是,当您拥有类型和同名的非类型元素时。通过使用详细的类型说明符,您可以显式请求类型:
struct foo {};
void foo(struct foo) {}
int main() {
struct foo f;
foo(f);
}
如果没有详细说明的类型说明符,foo
inmain
指的是void foo(struct foo)
,而不是 type struct foo
。现在,我不希望它出现在生产代码中,但您只要求提供一个重要的示例。如果类型和函数(或变量)被定义在不同的命名空间中,在这些命名空间中通过查找更早地找到了非类型,也会发生同样的情况。你可以struct
用enum
上面的替换。
下面解释的前向声明是 Elaborated Type Specifier 的一种表现形式。
在 C++ 和 Objective-C 等一些面向对象的语言中,有时需要前向声明类。这是在需要知道类的名称是类型但不需要知道结构的情况下完成的。
在 C++ 中,类和结构可以像这样前向声明:
class MyClass; struct MyStruct;
在 C++ 中,如果您只需要使用指向该类的指针类型,则可以前向声明类(因为所有对象指针的大小都相同,这是编译器所关心的)。这在类定义中特别有用,例如,如果一个类包含一个成员,该成员是另一个类的指针(或引用)。
前向声明用于避免不必要的耦合,这有助于通过减少包含头文件的数量来减少编译时间。这具有三重优势:
- 减少打开的文件数量
#include
(因此减少操作系统调用的数量)- 减少预处理文件的体积(因为不包括标题)
- 在修改前向声明的类时减少重新编译的影响。
如果您需要使用实际的类类型,则类的前向声明是不够的,例如,如果您有一个成员的类型直接是该类(不是指针),或者如果您需要将其用作基类,或者如果您需要在方法中使用类的方法。
这允许您使用指向类型的指针,而不必在头文件中包含相关的头文件(而您只需要在 .cpp 文件中使用它)。
这有助于减少依赖关系,从而提高编译时间。
并不总是需要它,尤其是Unreal Engine
核心类,因为它们通常都包含在内。但是,如果您没有明确包含头文件,则使用前缀是一种很好的做法。如果你这样做,如果任何包含头文件的文件更改为不包含它,你的代码仍然可以编译。
虽然前向申报很好,但您应该避免使用 ETS。如果你想转发声明你的类型,最好在文件范围内明确地这样做:
class Bad
{
void Func(class Y* YParam);
class Z* ZProp;
};
class Y;
class Z;
class Good
{
void Func(Y* YParam);
Z* ZProp;
};