在阅读访问者模式时,我遇到了这段代码:
virtual void visit(class Composite *, Component*) = 0;
Run Code Online (Sandbox Code Playgroud)
这是一个成员函数,它似乎是Composite在其参数内声明类.我只用一个普通的函数试过这个,就像这样:
void accept(class A a);
Run Code Online (Sandbox Code Playgroud)
对于A我尚未声明或定义的某些类,代码工作正常.为什么允许这样做?如果有的话,它是否与前线宣布不同?最近标准中有什么变化吗?
很多人都声称这是C的遗留物,但是为什么这个代码在C++中编译得很好,而不是C?
#include <stdio.h>
int process(struct A a);
struct A{
int x;
};
int process(struct A a){
return a.x;
}
int main(void)
{
struct A a = {2};
printf("%d", process(a));
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这称为不完整类型,是从C继承的概念C++.
不完整的类型以这种方式工作:在你class B的代码中定义a之前,你可以class B varname在函数原型中使用as作为参数,或者使用指向这种类型的指针class B* ptr- 在任何地方都不需要除了名称之外的类型的详细信息.
实际上,你可以用不同的方式编写它 - 在你将它用作一个不完整的类型之前class B;,只需要一个(它应该作为一个类声明),然后你可以写B varname而不是class B varname.
指向不完整类型的指针通常与不透明指针一起使用,这可能是C++中不完整类型的最常见用法.在链接的维基百科文章中对不透明的指针进行了充分的描述.简而言之,这是一种允许您的API隐藏整个类实现的技术.
使用您在问题中描述的不完整类型语法,来自维基百科的示例代码:
//header file:
class Handle {
public:
/* ... */
private:
struct CheshireCat; // Not defined here
CheshireCat* smile; // Handle
};
//CPP file:
struct Handle::CheshireCat {
int a;
int b;
};
Run Code Online (Sandbox Code Playgroud)
可以改写为:
//header file:
class Handle {
public:
/* ... */
private:
struct CheshireCat* smile; // Handle
};
//CPP file:
struct CheshireCat {
int a;
int b;
};
Run Code Online (Sandbox Code Playgroud)
请注意:这些代码片段不是等效的,因为前者定义了一个类型Handle::CheshireCat,而后者只是将其定义为CheshireCat.
以您提供的代码为例:
在C中,它不编译的原因很简单:struct A函数原型中的一个声明作用于函数原型,因此它与struct A后者声明的不同.对于这种特定情况,C和C++的规则略有不同.如果你向前声明struct如下:struct A;在函数原型之前,它将以两种语言编译!
此语法的其他显着用途:
作为C++ 向后兼容C的一部分,这种语法具有重要的地位.你可以在C中定义或向前声明struct这样的:struct A {};或者struct A;,类型的实际名称是struct A.要使用名称A,您需要使用typedef.C++确实后者自动,但是允许你使用A既作为struct A和A.同样适用于class-es union-s和enum-s.
实际上,有些人认为这具有语义上的重要性.考虑具有以下签名的函数:int asdf(A *paramname).你知道什么A是宣言吗?它是一个class,struct,enum或union?人们说这样的签名可以通过以下方式更清晰:int asdf(enum A *paramname).这是编写自我记录代码的好方法.
在C中,如果没有struct关键字,则无法访问结构名称:
struct Foo {};
void bar(struct Foo foo) {...}
Run Code Online (Sandbox Code Playgroud)
你可以通过使用一个来解决这个问题typedef:
typedef struct Foo {} Foo;
void bar(Foo foo) {...}
Run Code Online (Sandbox Code Playgroud)
在C++中,这仍然是为了向后兼容.它已被逻辑扩展,包括支持class关键字而不是struct.class Composite *几乎相当于Composite *在这方面说的话.它不一定用作前向声明,只需访问类型名称.
请注意,如有必要,它仍可用于消除歧义:
struct Foo {};
void Foo();
int main() {
Foo foo; //error: Foo refers to the function
struct Foo foo; //okay: struct Foo refers to the class
}
Run Code Online (Sandbox Code Playgroud)
现在,相同的声明可以引入类型名称,就像在您的accept示例中一样,并且可能在visit示例中.对于命名空间范围内的函数,如果未找到要声明的类,则将在包含该函数的命名空间中声明它(请参阅N4140 [basic.scope.pdecl]/7).
这意味着由于struct/ union不匹配,以下内容将无法编译:
void foo(struct Bar bar);
union Bar;
Run Code Online (Sandbox Code Playgroud)
上面的代码大致相当于:
struct Bar;
void foo(Bar bar);
union Bar;
Run Code Online (Sandbox Code Playgroud)