2015-07-15 2 views
14

При чтении о шаблоне посетителя я побежал в этот фрагмент кода:Почему разрешено форвардное объявление в объявлении функции?

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

Это, в основном, осталось от C. – chris

+1

Я не вижу, как 'visit' может быть переадресацией' Composite', указав его как параметр. Если что-то 'Composite' должно быть объявлено перед' visit' для его компиляции? – asimes

+0

Вот что я тоже думал, но это не так. – user975989

ответ

8

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

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

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

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

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

//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 и C++ имеют несколько разные правила для этого конкретного случая. Если вы переадресовываете struct следующим образом: struct A; перед прототипом функции, он будет скомпилирован на обоих языках!

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

Это syntaxis занимает важное место в рамках обратной совместимости С ++ с 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). Это хороший способ написания самодокументирующего кода.

4

В 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; 
+1

* "для некоторого класса A, который я еще не объявил или не определил, и код работал нормально." * Вопрос больше напоминает объявление неполного типа как части объявления функции. * «Это не форвардное объявление, просто доступ к имени типа« * на самом деле, для 'class Composite *' оно есть. –

+0

В C вы получите предупреждение с эквивалентным кодом: 'void visit (struct Composite *, struct Component *)' вводит новый тип 'struct Composite', который существует только в области описания функции, поэтому вы никогда не может завершить этот тип. Возможно, то же самое не относится к C++; когда я пытался скомпилировать код, G ++ set fussy не жаловался, но GCC настроился на суетливость. –

+0

@PiotrS., Позвольте мне добавить соответствующий текст по этому использованию. Я не думаю, что это почти так же распространено, как использование, упомянутое в ответе. – chris

Смежные вопросы