在阅读访问者模式时,我遇到了这段代码:
virtual void visit(class Composite *, Component*) = 0;
这是一个成员函数,它似乎是在其参数中声明类Composite
。我尝试使用普通函数,如下所示:
void accept(class A a);
对于我尚未声明或定义的某个类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;
}
答案 0 :(得分:9)
这称为不完整类型,是从C继承的概念C ++。
不完整类型以这种方式工作:在代码中定义class B
之前,可以使用class B varname
作为函数原型中的参数,或者使用指向此类型的指针作为{ {1}} - 除了名称之外没有关于类型的详细信息的任何地方。
实际上,你可以用不同的方式编写它 - 只需将class B* ptr
(它应该作为一个类声明)放在你用作不完整类型之前,然后你可以写{ {1}}代替class B;
。
指向不完整类型的指针通常与opaque pointers一起使用,这可能是C ++中不完整类型的最常见用法。在链接的维基百科文章中对不透明的指针进行了充分的描述。简而言之,这是一种允许API隐藏整个类实现的技术。
使用您在问题中描述的不完整类型语法,来自维基百科的示例代码:
B varname
可以改写为:
class B varname
请注意:这些代码段与不是等效,因为前者定义了类型//header file:
class Handle {
public:
/* ... */
private:
struct CheshireCat; // Not defined here
CheshireCat* smile; // Handle
};
//CPP file:
struct Handle::CheshireCat {
int a;
int b;
};
,而后者将其简单地定义为//header file:
class Handle {
public:
/* ... */
private:
struct CheshireCat* smile; // Handle
};
//CPP file:
struct CheshireCat {
int a;
int b;
};
。
以您提供的代码为例:
在C中,它不编译的原因很简单:函数原型中的Handle::CheshireCat
是一个作用于函数原型的声明,因此它与CheshireCat
不同。后者宣布。对于这种特定情况,C和C ++的规则略有不同。如果你在函数原型之前正向声明struct A
这样:struct A
,它将用两种语言编译!
此语法的其他显着用途:
这种语法作为C ++ 向后兼容C 的一部分具有重要地位。您可以在C中定义或向前声明struct
这样的内容:struct A;
或struct
,类型的实际名称为struct A {};
。要将名称用作struct A;
,您需要使用struct A
。 C ++会自动执行后者,但允许您将A
用作typedef
和A
。同样适用于struct A
- es A
- s和class
- s。
实际上,有些人认为这具有语义重要性。考虑具有以下签名的函数:union
。通过查看声明,你知道enum
是什么吗?是int asdf(A *paramname)
,A
,class
还是struct
?人们说这样的签名可以通过这种方式变得更加清晰:enum
。这是编写自我记录代码的好方法。
答案 1 :(得分:5)
在C中,如果没有struct
关键字,则无法访问结构名称:
struct Foo {};
void bar(struct Foo foo) {...}
您也可以使用typedef
来解决这个问题:
typedef struct Foo {} Foo;
void bar(Foo foo) {...}
在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
}
现在,相同的声明可以引入类型名称,就像在accept
示例中一样,并且可能在visit
示例中引用。对于命名空间作用域的函数,如果找不到声明的类,它将在包含该函数的命名空间中声明(参见N4140 [basic.scope.pdecl] / 7)。
这意味着由于struct
/ union
不匹配,以下内容无法编译:
void foo(struct Bar bar);
union Bar;
上述代码大致相当于:
struct Bar;
void foo(Bar bar);
union Bar;