为什么允许函数声明中的前向声明?

时间:2015-07-15 06:19:02

标签: c++ c++11

在阅读访问者模式时,我遇到了这段代码:

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;
}

2 个答案:

答案 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用作typedefA。同样适用于struct A - es A - s和class - s。

实际上,有些人认为这具有语义重要性。考虑具有以下签名的函数:union。通过查看声明,你知道enum是什么吗?是int asdf(A *paramname)Aclass还是struct?人们说这样的签名可以通过这种方式变得更加清晰:enum。这是编写自我记录代码的好方法。

答案 1 :(得分:5)

在C中,如果没有struct关键字,则无法访问结构名称:

struct Foo {};

void bar(struct Foo foo) {...}

您也可以使用typedef来解决这个问题:

typedef struct Foo {} Foo;

void bar(Foo foo) {...}

在C ++中,这仍然是为了向后兼容。它已经过逻辑扩展,包括支持class关键字而不是structclass 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;