Подтвердить что ты не робот

Почему разрешено форвардное объявление в объявлении функции?

При чтении о шаблоне посетителя я столкнулся с этим фрагментом кода:

virtual void visit(class Composite *, Component*) = 0;

Это функция-член, и она кажется, что она объявляет класс Composite внутри своих параметров. Я пробовал это с нормальной функцией, например:

void accept(class A a);

для некоторого класса A, который я еще не объявил или не определил, и код работал нормально. Почему это разрешено? Как, если он вообще отличается от прямого объявления перед линией? Что-то изменилось недавно в стандарте в отношении этого?


Многие люди утверждают, что это осталось от 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;
}
4b9b3361

Ответ 1

Это называется неполным типом и является понятием С++, унаследованным от C.

Неполные типы работают таким образом: прежде чем вы определили class B в своем коде, вы можете использовать class B varname как, скажем, аргумент в прототипах функций или использовать указатели на этот тип как class B* ptr - где угодно где не требуется никаких подробностей о типе, кроме его имени.

Собственно, вы можете написать его по-другому - просто поместите a class B; (который должен работать как объявление класса), прежде чем использовать его как неполный тип, а затем вы можете написать B varname вместо class B varname.

Указатели на неполные типы часто используются с непрозрачными указателями, которые, вероятно, являются наиболее распространенным использованием неполных типов в С++. Острые указатели описаны достаточно хорошо в связанной статье Википедии. Короче говоря, это метод, который позволяет вашему API скрывать всю реализацию класса.

Используя синтаксис неполного типа, который вы описываете в своем вопросе, пример кода из Википедии:

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

Обратите внимание: эти фрагменты кода не являются эквивалентными, поскольку первый определяет тип Handle::CheshireCat, а последний имеет его просто как CheshireCat.

В коде, который вы указали в качестве примера:

В C причина, по которой он не компилируется, довольно прост: struct A в прототипе функции - это объявление, привязанное к прототипу функции, и, следовательно, оно отличается от struct A, которое объявлено последним. C и С++ имеют несколько разные правила для этого конкретного случая. Если вы переадресовываете struct следующим образом: struct A; перед прототипом функции, он будет скомпилирован на обоих языках!

Другие заметные применения этого синтаксиса:

Этот синтаксис имеет важное место в качестве части обратной совместимости С++ с C. Вы видите, в C, после определения или переадресации объявления struct следующим образом: struct A {}; или struct A;, фактическое имя типа будет struct A. Чтобы использовать имя как A, вам нужно использовать typedef. С++ делает последнее автоматически, но позволяет использовать A как struct A и A. То же самое относится к class -es union -s и enum -s.

Собственно, некоторые утверждают, что это имеет семантическое значение. Рассмотрим функцию со следующей сигнатурой: int asdf(A *paramname). Знаете ли вы, что A просто просматривает декларацию? Это class, struct, enum или union? Люди говорят, что подобная подпись может быть сделана яснее: int asdf(enum A *paramname). Это хороший способ написания самодокументирующего кода.

Ответ 2

В C имена структур не были доступны без ключевого слова struct:

struct Foo {};

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

Вы можете обойти это, используя typedef:

typedef struct Foo {} Foo;

void bar(Foo foo) {...}

В С++ это остается для обратной совместимости. Он был логически расширен, включая поддержку ключевого слова 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;