>А в чем проблема в публичный интерфейс включить нужные инклюды?
>Интерфейс на то и интерфейс, чтобы определять всё необходимое для работы.
Так они и так там включаются…
Если рассматривать порядок включения заголовочных файлов в файлах реализации (cpp), то, действительно, Ваш подход может показать несамодостаточность заголовочных файлов. Но не всегда, и, особенно, в тех случаях, когда включений немало. Некоторые примеры Вы привели сами.
Самый верный способ проверить самостоятелен ли заголовочный файл — это подключить его одного в отдельном файле cpp, и, конечно, не подключать в него больше ничего.
Встречный вопрос, как Ваш подход применим при реализации библиотеки разработчиком (при поставке ее только в виде заголовочных файлов)?
>Самодостаточность файлов это включает в себя независимый порядок их включения, разве не так?
Конечно, так. Статья, как раз, и нацелена показать, что сделать самодостаточными каждый заголовочный файл с учетом всех зависимостей — «нудная» задача и ее можно решить по-другому.
(Кстати, прежде чем нажать кнопку «Написать», можно выбрать кнопку «Предпросмотр» и увидеть как будет выглядеть комментарий)
В данном порядке Ваш вариант не подходит, поскольку открытый (публичный) интерфейс очень часто зависит от сторонних библиотек (STL, boost и др.).
Но если его обратить, то вполне:
а) #include STL;
б) #include 3rd-party библиотек (boost, Qt, libpng и т.д.);
в) внутренние #include библиотеки SomeLib;
г) публичные #include библиотеки SomeLib.
Единственное исключение — это то, что варианты в) и г) могут перемешиваться. Бывает так, что детали реализации завязаны на открытый интерфейс. (еще раз повторюсь мы рассматриваем библиотеки в варианте заголовочных файлов).
Если реализацию методов классов выносить в отдельные файлы, то их, конечно, нужно подключать самыми последними.
Не скажу за всех, но лично в моем понятии модуль обеспечивает:
1) Явное разделение интерфейса и реализации.
2) Возможность импорта имен из другого модуля и создания псевдонимов импортируемых элементов.
3) Обособленность модулей. Т.е. изменения в одном модуле, не затрагивающие его интерфейса, не влияют на определения в других модулях.
4) Так называемые конструкторы/деструкторы модулей и, как следствие, определенный порядок их вызова.
По пункту 1.
в С++ имеется возможность разбивать исходные коды на заголовочные файлы (*.h, *.hpp) и файлы реализации (*.cpp). Также в этом помогают пространства имен и идиома PImpl, за использование которой приходится платить динамическим созданием, косвенным доступом к членам класса impl и дополнительным кодом.
По пункту 2.
В C++ нельзя задать псевдонимы для функций и для шаблонных классов (в С++11, наконец-то, появились alias-ы для шаблонных классов). А введение элементов из других пространств имен в заголовочных файлах, в общем случае, не приветствуется.
Пункт 3.
Если файлы реализации можно обрабатывать независимо друг от друга, то заголовочные файлы нельзя. Поэтому, если файл реализации зависит от нескольких «модулей», то заголовочные файлы каждого модуля компилируются вместе. Как следствие, возможны сюрпризы с порядком включения, #define-ами, специализациями шаблонов и другое.
Пункт 4.
По стандарту C++ порядок инициализации глобальных объектов не определен. Есть обходные пути, но не на все случаи.
Дополнительно хочется отметить, что введение модулей, по идее, должно значительно уменьшить время компиляции. Сейчас в C++ одно и то же компилируется по нескольку раз в каждой единице трансляции (прекомпилируемые заголовки не всегда помогают).
Автор вопроса при описании отличий своего варианта от решения 2 топика написал:
«что все оборачивается под одним неймспейсом без разделения (namespace my_lib пишется только один раз)».
Я возразил. Может быть, мысль была неудачно сформулирована.
И еще. Предлагаемый способ лишен одного недостатка (хотя для кого как), присущего всем остальным и, в частности, Вашему варианту. А именно: явное указание пространств имен при использовании классов других библиотек.
Помимо это, использование идиомы PImpl в стандартном для него виде, выливается в значительное количество кода, необходимого разработчику библиотеки для написания каждого такого класса.
Я прошу Вас, будьте внимательны. Автор вопроса указал разницу между предлагаемым им вариантом и решением 2, представленным в топике. Я этой разницы не заметил, указав на то место в описании варианта, при котором оба варианта становятся эквивалентными.
Сам написал не одну библиотеку (и не маленькие). Идеи написания шли рядом со стандартными, особенно с теми, что применялись в boost-e. Устал от сложности языка, а точнее, от тех шаблонов, к которым мы привыкли.
Перечитайте, пожалуйста, топик. В нем ясно сказано, что организация файлов в нем не рассматривается. Это будет сделано позже — в следующих топиках. Подождите, немного: сами все увидите…
По поводу PImpl. Заставлять пользователей всегда использовать динамическое выделение объекта класса реализации, это, уж извините, не очень хорошо. Пусть разработчик библиотеки решает, что ему лучше…
A1A3 B1B3 C1C3 A1A3 B1B3 A1A3 A2C2 A1C1 A3C3 A2C2
Весь код:
Тест пройден.
Много написанного, но хотелось довести данный вариант до конца. Просто не всегда хочется иметь зависимости от дополнительных библиотек.
Не учел.
Убираем const из специализации шаблона helper. Получаем:
Комментируем или правим четвертый блок static_assert-ов в представленном тесте.
Тест проходит.
Что касается четвертого блока assert-ов (для /**/const A /**/).
Непонятно почему все возвращаемые типы имеют &, а не &&?
Это не относится к реализации move_if_rr.
Уже не один раз в этой теме писали, что конструкция
не работает в качестве конструктора копирования.
Не учел… Несколько раз компилировал (проверить синтаксические ошибки)…
Вариант
не определяет конструктор копирования.
>Интерфейс на то и интерфейс, чтобы определять всё необходимое для работы.
Так они и так там включаются…
Если рассматривать порядок включения заголовочных файлов в файлах реализации (cpp), то, действительно, Ваш подход может показать несамодостаточность заголовочных файлов. Но не всегда, и, особенно, в тех случаях, когда включений немало. Некоторые примеры Вы привели сами.
Самый верный способ проверить самостоятелен ли заголовочный файл — это подключить его одного в отдельном файле cpp, и, конечно, не подключать в него больше ничего.
Встречный вопрос, как Ваш подход применим при реализации библиотеки разработчиком (при поставке ее только в виде заголовочных файлов)?
>Самодостаточность файлов это включает в себя независимый порядок их включения, разве не так?
Конечно, так. Статья, как раз, и нацелена показать, что сделать самодостаточными каждый заголовочный файл с учетом всех зависимостей — «нудная» задача и ее можно решить по-другому.
(Кстати, прежде чем нажать кнопку «Написать», можно выбрать кнопку «Предпросмотр» и увидеть как будет выглядеть комментарий)
Но если его обратить, то вполне:
а) #include STL;
б) #include 3rd-party библиотек (boost, Qt, libpng и т.д.);
в) внутренние #include библиотеки SomeLib;
г) публичные #include библиотеки SomeLib.
Единственное исключение — это то, что варианты в) и г) могут перемешиваться. Бывает так, что детали реализации завязаны на открытый интерфейс. (еще раз повторюсь мы рассматриваем библиотеки в варианте заголовочных файлов).
Если реализацию методов классов выносить в отдельные файлы, то их, конечно, нужно подключать самыми последними.
1) Явное разделение интерфейса и реализации.
2) Возможность импорта имен из другого модуля и создания псевдонимов импортируемых элементов.
3) Обособленность модулей. Т.е. изменения в одном модуле, не затрагивающие его интерфейса, не влияют на определения в других модулях.
4) Так называемые конструкторы/деструкторы модулей и, как следствие, определенный порядок их вызова.
По пункту 1.
в С++ имеется возможность разбивать исходные коды на заголовочные файлы (*.h, *.hpp) и файлы реализации (*.cpp). Также в этом помогают пространства имен и идиома PImpl, за использование которой приходится платить динамическим созданием, косвенным доступом к членам класса impl и дополнительным кодом.
По пункту 2.
В C++ нельзя задать псевдонимы для функций и для шаблонных классов (в С++11, наконец-то, появились alias-ы для шаблонных классов). А введение элементов из других пространств имен в заголовочных файлах, в общем случае, не приветствуется.
Пункт 3.
Если файлы реализации можно обрабатывать независимо друг от друга, то заголовочные файлы нельзя. Поэтому, если файл реализации зависит от нескольких «модулей», то заголовочные файлы каждого модуля компилируются вместе. Как следствие, возможны сюрпризы с порядком включения, #define-ами, специализациями шаблонов и другое.
Пункт 4.
По стандарту C++ порядок инициализации глобальных объектов не определен. Есть обходные пути, но не на все случаи.
Дополнительно хочется отметить, что введение модулей, по идее, должно значительно уменьшить время компиляции. Сейчас в C++ одно и то же компилируется по нескольку раз в каждой единице трансляции (прекомпилируемые заголовки не всегда помогают).
Только лучше не просто import, а полноценные модули в C++.
А по существу, и GCC и MSVC (и даже VisualDSP++) поддерживает данное расширение.
«что все оборачивается под одним неймспейсом без разделения (namespace my_lib пишется только один раз)».
Я возразил. Может быть, мысль была неудачно сформулирована.
И еще. Предлагаемый способ лишен одного недостатка (хотя для кого как), присущего всем остальным и, в частности, Вашему варианту. А именно: явное указание пространств имен при использовании классов других библиотек.
Помимо это, использование идиомы PImpl в стандартном для него виде, выливается в значительное количество кода, необходимого разработчику библиотеки для написания каждого такого класса.
Перечитайте, пожалуйста, топик. В нем ясно сказано, что организация файлов в нем не рассматривается. Это будет сделано позже — в следующих топиках. Подождите, немного: сами все увидите…
По поводу PImpl. Заставлять пользователей всегда использовать динамическое выделение объекта класса реализации, это, уж извините, не очень хорошо. Пусть разработчик библиотеки решает, что ему лучше…