Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Разбивка большого заголовочного файла на несколько
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
Сергей Борщ
Задача: есть некий большой заголовочный файл. Ну например:
Код
#ifndef HEADER_H__
#define HEADER_H__

class a
{
...
    class b
    {
    ...
    };
};

#endif // HEADER_H__

Сами классы довольно большие (в экран не влезают), плюс еще inline-функции, в общем файл получился большой. Ненавижу листать большие файлы, поэтому появилось желание разбить его на несколько:

Код
#ifndef HEADER_A_H__
#define HEADER_A_H__

class a
{
...
    class b;
};

#include "header_b.h"

.... // тут идут определения встраиваемых функций-членов класса a, которым нужно знать о внутреннем устройстве класса b

#endif // HEADER_A_H__

=================================

#ifndef HEADER_B_H__
#define HEADER_B_H__

class a::b
{
...
};
#endif // HEADER_B_H__

Но в этом случае header_b.h сам по себе интереса не представляет, его можно включать в исходник только через header_a.h
Для вменяемой диагностики неправильного включения обычно применяется такой метод:
Код
#ifndef HEADER_B_H__
#define HEADER_B_H__

#ifndef HEADER_A_H__
  #error "Include header_a.h instead of this file"
#endif

class a::b
{
...
};
#endif // HEADER_B_H__
Вроде все работает правильно, но не нравится мне один момент: в файле исходника, в котором определены невстраиваемые функции-члены класса a::b вместо логичного #include "header_b.h" придется включать header_a.h

Скорее всего я изобрел велосипед, но в header_b.h сделал так:
Код
#include "header_a.h"
#ifndef HEADER_B_H__
#define HEADER_B_H__

class a::b
{
...
};
#endif // HEADER_B_H__

Теперь я могу включать в любое нужное место как header_a.h, так и header_b.h и вместо сообщения об ошибке все будет корректно собираться, хотя тут есть рекурсия включений.

На мой взгляд все выглядит красиво, но возможно я не вижу каких-то подводных камней? Ваше мнение - имеет такой велосипед право на жизнь? Или может есть другие, еще более красивые решения?
CrimsonPig
Цитата(Сергей Борщ @ Feb 19 2015, 09:45) *
Задача: есть некий большой заголовочный файл. Ну например:
Сами классы довольно большие (в экран не влезают), плюс еще inline-функции, в общем файл получился большой. Ненавижу листать большие файлы, поэтому появилось желание разбить его на несколько:



Работает и ладно...
- можно вынести имплементрацию inline - функций в отдельный файл, типа *.inl и включать его в хедер где-нибудь под конец.
- раскидать хедеры для только внутреннего потребления и для внешнего по разным каталогам, шоб было больше проблем тому, кто хочет зачем-то включать приватные заголовочные файлы.
- открыть для себя редакторы с code folding sm.gif
- проблемы проектирования не решаются техническими методами. Если кто-то разродился классом, определение которого занимает 20 экранов, то тут не кровати переставлять надо, а пересматривать концепцию.
Сергей Борщ
Цитата(CrimsonPig @ Feb 19 2015, 13:20) *
- можно вынести имплементрацию inline - функций в отдельный файл, типа *.inl и включать его в хедер где-нибудь под конец.
Начинаются проблемы с подсветкой синтаксиса.

Цитата(CrimsonPig @ Feb 19 2015, 13:20) *
- открыть для себя редакторы с code folding sm.gif
Не спасает.
Цитата(CrimsonPig @ Feb 19 2015, 13:20) *
Если кто-то разродился классом, определение которого занимает 20 экранов, то тут не кровати переставлять надо, а пересматривать концепцию.
Пересмотрите концепцию стандартной библиотеки С++ sm.gif
CrimsonPig
Цитата(Сергей Борщ @ Feb 19 2015, 12:23) *
Пересмотрите концепцию стандартной библиотеки С++ sm.gif


Благородный дон в одиночку переделывает stl или boost ? Сурово.
Сергей Борщ
Цитата(CrimsonPig @ Feb 19 2015, 14:54) *
Благородный дон в одиночку переделывает stl или boost ?
Нет, всего лишь пишу USB-стек для STM32. stl привел как пример классов "на 20 экранов", концепцию которых пересматривать никто не собирается.
CrimsonPig
Цитата(Сергей Борщ @ Feb 20 2015, 13:26) *
Нет, всего лишь пишу USB-стек для STM32. stl привел как пример классов "на 20 экранов", концепцию которых пересматривать никто не собирается.


А при чем здесь концепция stl ? Если вынести имплементацию методов шаблонов в отдельный файл, то само определение класса с объявлением методов будет выглядеть вполне пристойно.
Никто не заставляет писать шаблонные классы + имплементацию методов в одном месте.
Я имел в виду, что если у класса 120 членов-данных разного типа + набор из 300 разномастных методов, которые непойми что делают, то тут не кровати переставлять надо.
Сергей Борщ
Цитата(CrimsonPig @ Feb 20 2015, 16:44) *
Если вынести имплементацию методов шаблонов в отдельный файл, то само определение класса с объявлением методов будет выглядеть вполне пристойно.
Если вынести в файл исходника, методы не будут встраиваемыми. Если вынести в заголовочный - то имеем проблему, описанную в первом сообщении.
CrimsonPig
Цитата(Сергей Борщ @ Feb 20 2015, 15:42) *
Если вынести в файл исходника, методы не будут встраиваемыми. Если вынести в заголовочный - то имеем проблему, описанную в первом сообщении.


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

В незамутненной теории надо публиковать чистые интерфейсы, а всю имплементацию прятать далеко-далеко. Но это надо много обдумывать и переделывать. Затраченные на это усилия не всегда оправдываются. То ли у Саттера, то ли у кого еще из современных столпов была книжка с главой "separating interface and implementation", там все это обсасывалось (включая паттерны типа pImpl).
mdmitry
Цитата(Сергей Борщ @ Feb 19 2015, 13:45) *
...
Скорее всего я изобрел велосипед, но в header_b.h сделал так:
...
Теперь я могу включать в любое нужное место как header_a.h, так и header_b.h и вместо сообщения об ошибке все будет корректно собираться, хотя тут есть рекурсия включений.

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


Сергей, для меня предпочтителен вариант :
Код
[/code]
#ifndef HEADER_B_H__
#define HEADER_B_H__

#include "header_a.h" <<<<<<<<< перемещено

class a::b
{
...
};
#endif // HEADER_B_H__

Перемещено, чтобы препроцессор обрабатывал гарантированно только вновь включаемые файлы. В этом случае рекурсия не убирается? Применяю именно такой подход.
Сергей Борщ
Цитата(mdmitry @ Feb 20 2015, 20:13) *
Сергей, для меня предпочтителен вариант :
Такой вариант работать будет только в том случае, если в исходник включается header_a.h. А если включается header_b.h, то он включит header_a.h, но внутри header_a.h, там, где ему требуется содержимое header_b.h, это содержимое включено не будет, так как #define HEADER_B_H__ уже было определено и будет куча ошибок. В моем варианте тоже нет рекурсии, каждый файл защищается своим #ifndef #define, но при этом при подключении к исходнику любого из них подключаются оба в правильном порядке.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.