Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > Forward Declaration / Forward Reference


Автор: SABROG 21.4.2009, 19:27
Вопрос теоретический. Еще раз напомню особенности для Forward Declaration. Без включения #include можно объявлять ссылки или указатели на объект любого класса таким образом:

Код

#ifndef MYHEADER_H
#define MYHEADER_H

class MyType;

class MyClass
{
public:
    MyClass();
    ~MyClass();

private:
MyType *pointer;
};

#endif // MYHEADER_H


Код


#include "myheader.h"

MyClass::MyClass()
{
    pointer = new MyType;
}

MyClass::~MyClass()
{

}


Я специально убрал #include, где прописан тип MyType, чтобы как-бы повысить скорость компиляци. Но в итоге то .cpp файл не будет компилиться, т.к.  ему все-равно нужно все знать о типе MyType. Вопрос в том, как тогда нужно сделать рефакторинг модулей, чтобы и #include убрать с объявлением типа и при этом не прописывать новый #include в .cpp файле. Я логику подобной оптимизации по скорости компиляции никак не уловлю. Или для такой структуры программы оптимизация не нужна в принципе, т.к. нечего оптимизировать - одно последовательное включение. А если хедер будет использоваться сразу в 10 .cpp файлах, неужели лучше (для скорости компиляции) прописать в каждом из десяти файлов - #include с объявлением MyType, вместо того, чтобы это сделать один раз в общем заголовочном файле?

Автор: zim22 21.4.2009, 19:31
не хотите все заголовки впихнуть в stdafx.h
установить в опциях компилятора Create/Use Precompiled Header
и потом в .cpp файле в первой строчке кода делать инклуд stdafx.h
?

Автор: SABROG 21.4.2009, 20:46
Я использую gcc. Насчет redunant include guards, precompiled headers, pragma once, multicore compilation и distribute compilation - осведомлен. Меня сейчас интересует именно вопрос с forward declaration.

Автор: mes 21.4.2009, 21:02
Цитата(SABROG @  21.4.2009,  18:27 Найти цитируемый пост)
А если хедер будет использоваться сразу в 10 .cpp файлах, неужели лучше (для скорости компиляции) прописать в каждом из десяти файлов - #include с объявлением MyType, вместо того, чтобы это сделать один раз в общем заголовочном файле? 

в этом разницы нет, что он одним файлом все 10 подключит, что по отдельности.
Но что лучше каждому из 10 cpp передавать по 2-3 разных хидера по 5кб или один общий  размером 50  кб ;)








Автор: SABROG 21.4.2009, 21:53
Цитата(mes @ 21.4.2009,  21:02)
Но что лучше каждому из 10 cpp передавать по 2-3 разных хидера по 5кб или один общий  размером 50  кб ;)

Ну так то вроде бы от перемены мест слагаемых сумма не меняется. А вот что будет быстрее для компилятора - 10 раз прочитать с винчестера 10 5кб-ных файлов или считать 50кб одним махом. Мне почему-то кажется второе. Например, если копировать сотни мелких файлов в винде это долгая операция в отличае от копирования одного файла, скажем .zip архива с нулевой степенью сжатия, где будут находится все эти файлы. Только у меня еще остается вопрос. Если поменять что-то в одном таком общем хедере, то перекомпиляция будет для всех .cpp файлов в которые он включен? А это уже будет обратный эффект тому ради чего создавался этот общий хедер.

Автор: mes 21.4.2009, 22:04
Цитата(SABROG @  21.4.2009,  20:53 Найти цитируемый пост)
. А вот что будет быстрее  прочитать с винчестера 10 5кб-ных файлов или считать 50кб одним махом

Как понял Вы думаете, что если хидеры включены в хидере, а не в .сpp  они один раз за всю компиляцию считываютаются.
неа.. точно также заново для каждой единицы трансляции (при оговоренном Вами выше условии, об отстствии прекомпиляции)
к тому же если тянут лишние хидеры, то в сумме получается выше и по кол-ву считываемыех файлов и по объему текста для парсера.

ну а если хотите съэкономить на считывании то добавьте второю пару стражей
Код

//file.h
#ifndef FILE_H_
#define FILE_H_

// file.cpp
#ifndef FILE_H_
#include "file.h"
#endif

 smile 

Автор: SABROG 21.4.2009, 22:16
Ну со стражами проблем у меня нет. Это наглядный пример redunant include guards (ну или pragma once).
Но, если возвращаться к forward declaration, то как должна выглядеть структура модулей, чтобы это благоприятно сказывалось на скорости компиляции (т.е. если забыть о первичном назначении forward declaration и сосредоточиться на побочном эффекте невключения лишних заголовков)?

Автор: J0ker 21.4.2009, 22:35
а с какого перепугу скорость компиляции должна увеличиться?

Автор: mes 21.4.2009, 22:44
Цитата(SABROG @  21.4.2009,  21:16 Найти цитируемый пост)
Ну со стражами проблем у меня нет. Это наглядный пример redunant include guards (ну или pragma once).

Судя по высказыванию Вы не разобрались с примером. Никто не говорил что у Вас проблемы со стражами.
Просто при обычном(одинарном) использовании страж, вначале считывается файл, потом проверяется включен ли он уже.
А при двойном, как в примере выше, вначале проверяется включен ли уже такой файл и если не включен, то тогда происходит считывание.
Так что эффект от подобного использования есть и в инете я помойму даже видел сравнительную таблицу.

Цитата(SABROG @  21.4.2009,  21:16 Найти цитируемый пост)
Но, если возвращаться к forward declaration, то как должна выглядеть структура модулей,... на побочном эффекте невключения лишних заголовков

Все как и обычно, только в хидеры ничего лишнего не подключать.
ну и так как  создавать объекты "вперед объявленнoго" класса нельзя, то следовательно, для случая
Код

class B {    A m_a; }

придется приглашать указатели и new/delete
Код

class B { A * p_a; ..}
B::B() { p_a = new A; }


Добавлено @ 22:49
Цитата(J0ker @  21.4.2009,  21:35 Найти цитируемый пост)
а с какого перепугу скорость компиляции должна увеличиться?

самое интересное что скорость  увеличивается smile
точнее сказать время компиляции уменьшается smile

Автор: Alek86 21.4.2009, 22:58
Цитата(mes @  21.4.2009,  22:44 Найти цитируемый пост)
самое интересное что скорость  увеличивается

дык с какого перепугу?
forward declarations нужны для уменьшения связи между модулями
то есть при изменении одного ашника в большом проекте, использущем forward declarations нужно будет перекомпиливать меньше модулей, чем в том же проекте, в котором бездумно понапиханы инклуды

для увеличения скорости компиляции используются precompiled headers
как они помогают увеличивать скорость, я не очень понимаю
наверное компилер при создании прекомпилед pdb файла приводит их в какой-то полускомпиленный (для шаблонов) вид, который помогает ускорять инстанцирование

Автор: mes 21.4.2009, 23:09
Цитата(Alek86 @  21.4.2009,  21:58 Найти цитируемый пост)
дык с какого перепугу?

меньше модулей подключено -> меньше считывается+меньше парсится. 

Цитата

My experiment was slightly different.  Instead of testing on an existing co debase, I created a test project of 200 header files, every one of which included all the others, and a single cpp file that includes all headers and an empty main function.  I tested timing by turning on build timing in Visual Studio (Tools -> Options -> Projects -> VC++ Build -> Build Timing), then by right-clicking on the main.cpp file and selecting compile.  My timings were as follows:

    * Nothing - 28 seconds
    * #pragma once - 17 seconds
    * Redundant include guards - < 1 second


http://www.gamearchitect.net/Articles/ExperimentsWithIncludes.html

сам я тоже убедился на личном опыте, у меня в проекте было примерно 100 файлов и после тщательной проверки инклудов со применением forward declaration
время сократилось в  почти два раза, точные значения шас не помню, но примерно вместо  2 минут 20 стал компилится 1 min 20.


Цитата(Alek86 @  21.4.2009,  21:58 Найти цитируемый пост)
то есть при изменении одного ашника в большом проекте,

да от этого выгода намного заметнее (так как чaстичная перекомпиляция намного чаще чем полная smile ) и в принципе основное из за чего я использую. smile


Автор: Alek86 21.4.2009, 23:35
а, точно, еще парсинг
тогда да, есть выгода
хотя странно, что такая огромная

Автор: mes 21.4.2009, 23:45
Цитата(Alek86 @  21.4.2009,  22:35 Найти цитируемый пост)
тогда да, есть выгода
хотя странно, что такая огромная


Ну а какой ей быть если вместо n нужных строк приходится компилить k*n строк smile 
все зависит от кол-ва включенного лишнего кода smile

Автор: SABROG 22.4.2009, 00:03
Цитата(mes @  21.4.2009,  22:44 Найти цитируемый пост)
class B { A * p_a; ..}
B::B() { p_a = new A; }

Если эти 2 строки разделить по модулям .h и .cpp. То ошибка будет при компиляции .cpp файла, т.к. тип A нигде не определен, а следовательно #include с объявлением A таким образом просто переносится из .h файла в .cpp файл(ы). Так ведь? А тогда где выйгрыш?

До:

user posted image

После:

user posted image

P.S.: по UML также как и по C++ у меня 3 с натяжкой smile

Автор: mes 22.4.2009, 00:16
Цитата(SABROG @  21.4.2009,  23:03 Найти цитируемый пост)
Если эти 2 строки разделить по модулям .h и .cpp. То ошибка будет при компиляции .cpp файла, т.к. тип A нигде не определен, а следовательно #include с объявлением A таким образом просто переносится из .h файла в .cpp файл(ы). Так ведь? А тогда где выйгрыш?

В нарисованном вами случаем нигде. А теперь представьте для 100 хидеров.
Ведь сппшнику необходимы далеко не все файлы. 
Тогда как первом случае в каждый cpp-шних будут включены в каждый все 100, а во 2м также по 2-4 хидера smile
Чтоб лучше представить, на 2й вашей картинке только две линии  про cpp-юнит оставьте. 

Автор: J0ker 22.4.2009, 00:43
абсолютно соглаен, что выигрыш в парсинге
никакого выигрыша в самой компиляции не получится
просто не ожидал такой большой разницы...

Автор: SABROG 22.4.2009, 08:32
Цитата(mes @  22.4.2009,  00:16 Найти цитируемый пост)
Тогда как первом случае в каждый cpp-шних будут включены в каждый все 100, а во 2м также по 2-4 хидера smile
Чтоб лучше представить, на 2й вашей картинке только две линии  про cpp-юнит оставьте. 


Теперь я понял, как-раз о такой структуре модулей я вопрашал создавая эту тему. Т.е. единственный правильный вариант при котором можно добиться оптимизации

user posted image

Цитата(J0ker @  22.4.2009,  00:43 Найти цитируемый пост)
абсолютно соглаен, что выигрыш в парсинге
никакого выигрыша в самой компиляции не получится
просто не ожидал такой большой разницы... 

А разве выйгрыш в скорости парсинга как следствие не дает выйгрыш в скорости компиляции?

Автор: mes 22.4.2009, 08:51
Цитата(SABROG @  22.4.2009,  07:32 Найти цитируемый пост)
Теперь я понял, как-раз о такой структуре модулей я вопрашал создавая эту тему. Т.е. единственный правильный вариант при котором можно добиться оптимизации

Да.. только не обязательно чтоб от cppшника была только одна линия. Главное, чтоб не было включено ничего лишнего. И из за "необработки"  это лишнего и будет экономия smile

Добавлено через 5 минут и 24 секунды
Цитата(SABROG @  22.4.2009,  07:32 Найти цитируемый пост)

А разве выйгрыш в скорости парсинга как следствие не дает выйгрыш в скорости компиляции?

тут игра слов, зависит от точки зрения,  что именно считать компиляцией. Весь этап от вместе с чтением, парсингом  и препроцессором или чисто компиляцию.
И к тому же _скорость_ компиляции остается вообще без изменения. Изменяется объем компиляции и, как следствие, _время_ компиляции.  smile 

Автор: SABROG 22.4.2009, 09:15
Цитата(mes @  22.4.2009,  08:51 Найти цитируемый пост)
тут игра слов, зависит от точки зрения,  что именно считать компиляцией. Весь этап от вместе с чтением, парсингом  и препроцессором или чисто компиляцию.
И к тому же _скорость_ компиляции остается вообще без изменения. Изменяется объем компиляции и, как следствие, _время_ компиляции.


Значит, если говорить об "общем времени компиляции", то выйгрыш будет? Должен ведь быть smile

Автор: Earnest 22.4.2009, 09:17
Освобождение от лишних зависимостей (включая использование forward declarations вместо полных инклудов) - это очень хорошая привычка, полезность которой весьма заметна на больших проектах. Если у вас три с половиной файла, которые сильно между собой связаны, то разницы вы не увидите. Но привычку надо вырабатывать - будут и большие проекты. Сильные зависимости плохи не только для скорости компиляции (в конце концов, мощность машин растет). Гораздо сильнее они мешают при внесении изменений в дизайн. Поэтому нужно просто привыкнуть делать все так локально как только возможно. Кроме прочего, получите еще и лучший дизайн, если избавитесь от привычки подключать все, что в голову пришло.

Автор: mes 22.4.2009, 09:49
Цитата(SABROG @  22.4.2009,  08:15 Найти цитируемый пост)
начит, если говорить об "общем времени компиляции", то выйгрыш будет? Должен ведь быть smile 

 smile 

Earnest,  smile 

Автор: math64 22.4.2009, 10:03
Код

// a.h
class A {
};
// b.h
class A;
class B {
A* a;
public:
B();
};
// b.cpp
#include "a.h"
#include "b.h"
B::B() { a = new A(); }
// c.h
class B;
class C {
B* b;
public:
C();
};
// c.cpp
// #include "a.h" // не нужен
#include "b.h"
#include "c.h"
C::C() { b = new B(); }


Автор: J0ker 22.4.2009, 16:18
Цитата(Earnest @  22.4.2009,  09:17 Найти цитируемый пост)
включая использование forward declarations вместо полных инклудов

ну тут и свои минусы есть
например в VS "Go to Definition" отправит вас к forward declaration, а не туда куда нужно

Автор: zim22 22.4.2009, 16:27
Цитата(J0ker @  22.4.2009,  16:18 Найти цитируемый пост)
например в VS "Go to Definition" отправит вас к forward declaration, а не туда куда нужно

а если Visual Assist подключить?

Автор: J0ker 22.4.2009, 17:55
Цитата(zim22 @ 22.4.2009,  16:27)
Цитата(J0ker @  22.4.2009,  16:18 Найти цитируемый пост)
например в VS "Go to Definition" отправит вас к forward declaration, а не туда куда нужно

а если Visual Assist подключить?

не знаю не пользовался, но подозреваю что не поможет, т.к. это не глюк, а формально правильное поведение - forward declaration и реалюное объявление - формально разные типы

Автор: Любитель 22.4.2009, 19:08
Как раз правильней различать Declaration и Definition ;)

Автор: J0ker 22.4.2009, 19:32
Цитата(Любитель @ 22.4.2009,  19:08)
Как раз правильней различать Declaration и Definition ;)

все претензии к VS - в случае не включения хедеров в проект она видит только forward declaration в обоих случаях (что, впрочем, и понятно)

Автор: Alek86 22.4.2009, 19:41
J0ker, помогает
сначала идет к объявлению, а по второму нажатию - к определению

Автор: Любитель 22.4.2009, 19:46
Цитата(J0ker @  22.4.2009,  19:32 Найти цитируемый пост)
все претензии к VS

Я не против. Просто я к тому, что это логически не правильно - это баг строо говоря.

Автор: J0ker 22.4.2009, 19:49
Цитата(Alek86 @ 22.4.2009,  19:41)
J0ker, помогает
сначала идет к объявлению, а по второму нажатию - к определению

только если у вас оба хедера включены (прямо или косвенно) в проект, и InteliSense еще не переклинило (что на больших и не очень проектах бывает с ним довольно часто)

Добавлено через 14 минут и 27 секунд
Цитата(Любитель @ 22.4.2009,  19:46)
Цитата(J0ker @  22.4.2009,  19:32 Найти цитируемый пост)
все претензии к VS

Я не против. Просто я к тому, что это логически не правильно - это баг строо говоря.

не понял
где баг?
я хочу посмотреть на определение и жму на "Go to Definition" а попадаю на forward declaration
я-то тут при чем

Автор: Любитель 22.4.2009, 20:23
Баг VS, а не
Цитата(J0ker @  22.4.2009,  17:55 Найти цитируемый пост)
формально правильное поведение


Автор: Alek86 22.4.2009, 20:46
Цитата(J0ker @  22.4.2009,  19:49 Найти цитируемый пост)
только если у вас оба хедера включены (прямо или косвенно) в проект

а как ты себе представляешь использование класса, если его хедер нигде не включен в проекте?

Автор: J0ker 22.4.2009, 21:01
Цитата(Alek86 @  22.4.2009,  20:46 Найти цитируемый пост)
а как ты себе представляешь использование класса, если его хедер нигде не включен в проекте? 

вот такой я любопытный  smile 

Автор: math64 23.4.2009, 09:39
Цитата(Alek86 @  22.4.2009,  20:46 Найти цитируемый пост)
а как ты себе представляешь использование класса, если его хедер нигде не включен в проекте?

Класс определён в dll, а в проекте только forward declaration

Автор: Alek86 23.4.2009, 10:23
math64, и как компилер скомпилит обращение к какой-либо паблик функции этого класса, без ашника?

Автор: math64 23.4.2009, 10:39
Классы А и В - dll, A* a - private поле в классе В.
Класс C в проекте использует класс B, инкюдит заголовок В, в котором только предварительное объявление class A;
Работа с классом A - только через public методы класса B

Автор: J0ker 23.4.2009, 16:02
Цитата(math64 @  23.4.2009,  10:39 Найти цитируемый пост)
Классы А и В - dll

или просто lib или даже obj

Автор: kamre 23.4.2009, 17:34
Цитата(math64 @ 23.4.2009,  10:39)
Классы А и В - dll, A* a - private поле в классе В.
Класс C в проекте использует класс B, инкюдит заголовок В, в котором только предварительное объявление class A;
Работа с классом A - только через public методы класса B

http://en.wikipedia.org/wiki/Opaque_pointer?

Автор: Alek86 23.4.2009, 23:05
Цитата(math64 @  23.4.2009,  10:39 Найти цитируемый пост)
Классы А и В - dll, A* a - private поле в классе В.Класс C в проекте использует класс B, инкюдит заголовок В, в котором только предварительное объявление class A;Работа с классом A - только через public методы класса B

правда тогда ты вообще знаешь о существовании класса A только из-за его объявления в заголовке B
возможно, разработчики ассиста посчитали лишней тратой времени пытаться искать не нужный разработчику класс A по всему солюшену (кстати, его определения в солюшене может и не оказаться, а может оказаться несколько разных)

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)