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

use*_*989 15 c++ c++11

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

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)

Min*_*s97 9

这称为不完整类型,是从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 AA.同样适用于class-es union-s和enum-s.

实际上,有些人认为这具有语义上的重要性.考虑具有以下签名的函数:int asdf(A *paramname).你知道什么A是宣言吗?它是一个class,struct,enumunion?人们说这样的签名可以通过以下方式更清晰:int asdf(enum A *paramname).这是编写自我记录代码的好方法.


chr*_*ris 5

在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)

  • @DeanSeo:嗯,两个,我想.它将`struct Composite`作为一个类型引入,因此`struct Composite*`作为指向该类型的指针.但是因为在函数原型之前没有提到`struct Composite`,所以在C中是错误的(所以GCC抱怨:`cc.c:2:19:错误:'struct Composite'在参数列表中声明[-Werror] void visit(struct Composite*,Component*); cc.c:2:19:error:它的范围只是这个定义或声明,可能不是你想要的[-Werror]`;第1行包含`typedef struct Component Component ;`). (3认同)
  • *“对于我尚未声明或定义的某些A类,代码运行正常。” *问题看起来更多是关于将不完整类型声明为函数声明的一部分。*“实际上,它不是前向声明,只是访问类型名称”,对于“类Composite *”来说,是这样。 (2认同)