Как избежать столкновения пространства имен в C и С++

Я могу использовать директиву using namespace, чтобы избежать столкновения имен идентификаторов/переменных, но что происходит, когда имена файлов или имена библиотек происходят в больших проектах.

В C традиционный подход заключается в том, чтобы рекурсивно добавлять файлы с помощью директивы #include_next. Как я могу достичь того же значения в C++, не используя директиву #include_next, и решить проблему дублирования имен файлов среди приложений и разделяемых библиотек. Например, работа над функцией класса() в AIX math.h происходит с идентификаторами с именем "класс".

/* GNU Lesser GPLv2.1 or later */
#ifndef FFMPEG_COMPAT_AIX_MATH_H
#define FFMPEG_COMPAT_AIX_MATH_H

#define class some_text

#include_next <math.h>

#undef class

#endif /* FFMPEG_COMPAT_AIX_MATH_H */

EDIT: Могу ли я использовать, например, class machine-instruction-set, где двоичный файл должен запускаться на нескольких платформах? Может ли быть столкновение пространства имен в таком случае?

Ответ 1

Я могу использовать с помощью директивы namespace, чтобы избежать столкновения имен идентификаторов/переменных

Наоборот, директива using namespace вводит коллизии. Вы разрешаете конфликты, указывая области действия, например. std::vector<> против boost::numeric::ublas::vector<>.

... но что происходит, когда имена файлов или имена библиотек происходят в больших проектах?

Столкновения имен файлов легко предотвратить, систематически: организовать заголовки, чтобы они имитировали ваши пространства имен, например. boost::numeric::ublas::vector<> происходит от #include <boost/numeric/ublas/vector.hpp>. И не складывайте заголовки и источники разных библиотек в один каталог, чтобы вы могли включать заголовки с тем же именем, используя другой префикс каталога, например. #include <lzma/version.h> против #include <linux/version.h>.

Ответ 2

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

Рекомендации по написанию библиотек

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

Рассмотрим структуру каталогов Boost. На верхнем уровне Boost вводит только одно имя в путь поиска include: каталог boost. Таким образом, даже если boost вводит несколько имен файлов заголовков, которые могут столкнуться (например, array.hpp, thread.hpp или function.hpp)), они все завернуты в подкаталог:

#include <boost/thread.hpp> // this is boost header
#include "thread.hpp"       // some header specific to my current project
                            // no name clash :)

Такая же концепция используется для разных библиотек, которые поставляются с Boost. Например, как Boost lockfree, так и Boost назначают заголовок queue.hpp. Но они живут в разных подкаталогах, поэтому нет столкновения:

#include <boost/lockfree/queue.hpp>
#include <boost/assign/std/queue.hpp>   // no clash :)

Чтобы упростить поиск правильного файла заголовка, Boost использует ту же структуру для include файлов и пространств имен: очередь без блокировки живет в пространстве имен boost::lockfree, а функции из заголовка очереди назначения - boost::assign. Таким образом, не просто найти подходящее пространство имен из файла include и наоборот, это также уменьшает вероятность конфликтов пространства имен, поскольку столкновение пространства имен также может проявляться в конфликте физического имени на уровне файла.

Вы можете адаптировать эти рекомендации для своего собственного проекта.

  • Не вводите файлы с открытым включением непосредственно в путь include. Вместо этого группируйте их вместе в каталог, чтобы избежать загрязнения пути include с именами.
  • Используйте деревья каталогов для дальнейшей структуры включенных файлов модулей внутри одной библиотеки.
  • Попробуйте сопоставить физическую структуру с логической структурой. Файловая система включает в себя пути, которые должны походить на иерархии пространства имен, если это возможно.

Это позволяет избежать большинства названий в первую очередь. Вопрос в том, что, если вам нужно использовать сторонние библиотеки, которые не следуют этим правилам, и вы получаете столкновение, которое находится вне вашего контроля?

Рекомендации по устранению конфликтов имен со стороны сторонних библиотек

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

Держитесь подальше от специальных исправлений типа #include_next для исправления физических столкновений или препроцессора для устранения логических столкновений. Они грязные хаки, и хотя они могут временно решить вашу проблему, они, скорее всего, вернутся и укусят вас в конце концов.

Ответ 3

Сопоставление имен файлов

Поместите библиотеки в отдельные подкаталоги и установите родительский каталог в качестве места поиска. Поэтому вместо:

#include "zerz.h"  // supposed to be from libfoo
#include "zerz.h"  // supposed to be from libbar

Вы можете сделать это:

#include "libfoo/zerz.h"
#include "libbar/zerz.h"

Идентификационные коллизии

Используйте pimpl idiom, чтобы изолировать код, который взаимодействует с каждой библиотекой, чтобы конфликтующие идентификаторы не перетаскивались в дваллионы проектов из-за переходный включает.

Например, если libfoo и libbar имеют функцию с именем Frobnicate, то вы хотите изолировать любой код, который зависит от этих библиотек, чтобы ничто не должно было включать заголовки этих библиотек и, следовательно, в итоге конфликтует. Только файл .cpp(или .c), который на самом деле вызывает Frobnicate, должен # включать заголовочный файл библиотеки. Это предотвращает непреднамеренное переходное включение, которое обычно заключается в том, что в конечном итоге конфликтующие объявления Frobnicate включаются в единый блок компиляции.

Идиома pimpl обычно представляет собой термины С++, но вы можете играть в ту же игру в C. Цель состоит в том, чтобы определить свой собственный API для библиотеки в заголовке. Ваш API должен обладать одинаковой функциональностью, но использовать имена, которые не конфликтуют (например, путем добавления уникального префикса, если вы не можете использовать пространства имен). Весь код должен иметь возможность включать этот заголовок без конфликтов. Затем вы предоставляете файл реализации (.cpp или .c), который является единственным файлом, в котором # включает фактический заголовок библиотеки. Этот файл реализации по существу пересылает вызовы префиксных функций на фактические функции библиотеки. Все, что вам нужно сделать, это избежать столкновений в этом файле, что должно быть выполнимо.

Ответ 4

Прежде всего, #include_next не является стандартным, это расширение gcc, также поддерживаемое Clang, но я не знаю, поддерживают ли его другие компиляторы.

Единственный способ справиться с конфликтами имен файлов - правильно использовать либо #include "foo.h", либо #include <foo.h>. Первый должен быть локальным для текущего проекта, а последний используется для системных библиотек. Обычно #include "foo.h" также будет искать системные библиотеки, но не наоборот.

В любом случае, #include "foo.h" следует начинать с поиска в каталоге исходного файла, вы можете использовать #include "../foo.h" и т.д. для включения относительных включений. Если у вас есть конфликты имен файлов в рамках одного и того же проекта, вам придется использовать разные флаги компиляции для установки разных путей поиска (в основном сделать подпроекты).

Для конфликтов символов #define до #include является стандартным способом.

Конечно, лучший способ - сначала позаботиться о том, чтобы избежать таких проблем.

Ответ 5

Полезный метод, который следует использовать при использовании C в программах на С++, заключается в использовании следующего фрагмента кода в ваших файлах .h.

#ifdef __cplusplus
extern "C" {
#endif

<your code here...>

#ifdef __cplusplus
}
#endif

Это позволит вашим объектам С++ связываться с объектами C. Я не знаю, как идти другим путем, так что, возможно, это частичный ответ.

Ответ 6

"Я могу использовать директиву using namespace, чтобы избежать столкновения имен идентификаторов/переменных": Нет! Вы должны избегать использования этой директивы, чтобы избежать столкновений имен.