Использование пространства имен C ++ увеличивает связь? - PullRequest
7 голосов
/ 04 февраля 2010

Я понимаю, что библиотека C ++ должна использовать пространство имен, чтобы избежать конфликтов имен, но так как мне уже нужно:

  1. #include правильный заголовок (или форвард объявите классы, которые я намерен использовать)
  2. Используйте эти классы по имени

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


Например, посмотрите на Xerces-C: он определяет чисто виртуальный интерфейс с именем Parser в пространстве имен XERCES_CPP_NAMESPACE. Я могу использовать интерфейс Parser в своем коде, включив соответствующий заголовочный файл и затем либо импортировав пространство имен using namespace XERCES_CPP_NAMESPACE, либо предварительно добавив объявления / определения с помощью XERCES_CPP_NAMESPACE::.

По мере развития кода, возможно, придется отказаться от Xerces в пользу другого парсера. Я частично «защищен» от изменений в реализации библиотеки чисто виртуальным интерфейсом (тем более, если я использую фабрику для создания моего анализатора), но как только я переключаюсь с Xerces на что-то другое, мне нужно прочесать мой код и изменить весь мой код using namespace XERCES_CPP_NAMESPACE и XERCES_CPP_NAMESPACE::Parser.


Я столкнулся с этим недавно, когда я реорганизовал существующий проект C ++, чтобы разделить некоторые существующие полезные функции в библиотеку:

foo.h

class Useful;  // Forward Declaration

class Foo
{
public:

    Foo(const Useful& u);
    ...snip...

}

foo.cpp

#include "foo.h"
#include "useful.h" // Useful Library

Foo::Foo(const Useful& u)
{
    ... snip ...
}

В значительной степени из-за невежества (и частично из-за лени) в то время вся функциональность useful.lib была помещена в глобальное пространство имен.

По мере роста содержания useful.lib (и все больше клиентов начали использовать эту функциональность) было решено переместить весь код из useful.lib в его собственное пространство имен с именем "useful".

Клиентские .cpp файлы было легко исправить, просто добавьте using namespace useful;

foo.cpp

#include "foo.h"
#include "useful.h" // Useful Library

using namespace useful;

Foo::Foo(const Useful& u)
{
    ... snip ...
}

Но файлы .h были действительно трудоемкими. Вместо того, чтобы загрязнять глобальное пространство имен, помещая using namespace useful; в заголовочные файлы, я обернул существующие предварительные объявления в пространство имен:

foo.h

namespace useful {
    class Useful;  // Forward Declaration
}

class Foo
{
public:

    Foo(const useful::Useful& u);
    ...snip...
}

Были десятки ( и десятки ) файлов, и это стало большой болью! Это не должно было быть так сложно. Очевидно, что я сделал что-то не так с дизайном и / или реализацией.

Хотя я знаю, что код библиотеки должен находиться в своем собственном пространстве имен, было бы выгодно, чтобы код библиотеки оставался в глобальном пространстве имен и вместо этого попытался управлять #includes?

Ответы [ 7 ]

10 голосов
/ 04 февраля 2010

Мне кажется, что ваша проблема связана прежде всего с тем, как вы (ab) используете пространства имен, а не с самими пространствами имен.

  1. Звучит так, как будто вы выбрасываете много «минимально связанных» «вещей» в одно пространство имен, в основном (когда вы к нему подходите), потому что они были разработаны одним человеком. По крайней мере, IMO, пространство имен должно отражать логическую организацию кода, а не просто случай, когда несколько утилит были написаны одним и тем же человеком.

  2. Имя пространства имен, как правило, должно быть достаточно длинным и описательным, чтобы не допустить более чем самую отдаленную возможность столкновения. Например, я обычно включаю свое имя, дату написания и краткое описание функциональности пространства имен.

  3. Большинству кода клиента не нужно (и часто не следует) использовать настоящее имя пространства имен напрямую. Вместо этого он должен определять псевдоним пространства имен, и в большинстве кодов должно использоваться только имя псевдонима.

Сложив точки два и три вместе, мы можем получить код наподобие этого:

#include "jdate.h"

namespace dt = Jerry_Coffin_Julian_Date_Dec_21_1999;

int main() {

    dt::Date date;

    std::cout << "Please enter a date: " << std::flush;
    std::cin>>date;

    dt::Julian jdate(date);
    std::cout   << date << " is " 
                << jdate << " days after " 
                << dt::Julian::base_date()
                << std::endl;
    return 0;
}

Это устраняет (или, по крайней мере, радикально уменьшает) связь между клиентским кодом и конкретной реализацией классов даты / времени. Например, если бы я хотел повторно реализовать те же классы даты / времени, я мог бы поместить их в другое пространство имен и переключаться между ними, просто изменяя псевдоним и перекомпилируя.

На самом деле, я иногда использовал это как своего рода механизм полиморфизма во время компиляции. Для одного примера я написал пару версий небольшого класса «display», один из которых отображает вывод в списке Windows, а другой - вывод через iostreams. Затем код использует псевдоним что-то вроде:

#ifdef WINDOWED
namespace display = Windowed_Display
#else
namespace display = Console_Display
#endif

В остальной части кода просто используется display::whatever, поэтому, пока оба пространства имен реализуют весь интерфейс, я могу использовать любое из них, не меняя остальной код вообще, и без каких-либо накладные расходы времени выполнения от использования указателя / ссылки на базовый класс с виртуальными функциями для реализаций.

9 голосов
/ 04 февраля 2010

Пространство имен не имеет ничего общего со связью. Такая же связь существует, называете ли вы ее useful::UsefulClass или просто UsefulClass. Теперь тот факт, что вам нужно было выполнить всю эту работу по рефакторингу, говорит только о том, насколько ваш код зависит от вашей библиотеки.

Чтобы упростить пересылку, вы могли бы написать заголовок forward (в STL есть пара, вы наверняка найдете его в библиотеках), например usefulfwd.h, который только переадресовывал интерфейс библиотеки (или реализовывал классы тебе нужно). Но это не имеет ничего общего со связью.

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

6 голосов
/ 04 февраля 2010

(a) интерфейсы / классы / функции из библиотеки

Не больше, чем у вас уже есть. Использование namespace -ed библиотечных компонентов поможет вам избежать загрязнения пространства имен.

(b) детали реализации, определяемые пространством имен?

Почему? Все, что вы должны включить, это заголовок useful.h. Реализация должна быть скрытой (и находиться в useful.cpp и, вероятно, в форме динамической библиотеки).

Вы можете выборочно включать только те классы, которые вам нужны от useful.h, имея using useful::Useful объявлений.

2 голосов
/ 05 февраля 2010

Я хотел бы подробнее остановиться на втором абзаце Дэвида Родригеса - ответ дрибея (upvoted):

Чтобы упростить переадресацию, вы могли бы написать заголовок пересылки (в STL есть пара, вы, несомненно, можете найти ее в библиотеках), например, goodfwd.h, который только переадресовывает определенный интерфейс библиотеки (или реализует классы или все, что вам нужно) ). Но это не имеет ничего общего со связью.

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

Я могу понять вашу «лень»: это - это , это не правильно для overengineer ( enterprise HelloWorld.java), но если вы вначале сохраняете свой код низкопрофильным (который не обязательно неправильно), и код окажется успешным, успех перетянет его выше своей лиги. хитрость заключается в том, чтобы определить подходящий момент для переключения (или использовать с первого момента, когда появляется необходимость) технику, которая царапает ваш зуд совместимым образом вперед .

Сверкающие форвардные объявления по проекту - это просто умоление о втором и последующих раундах. Вам не нужно быть программистом на C ++, чтобы прочитать совет «не объявляйте заранее стандартные потоки, вместо этого используйте <iosfwd>» (хотя это было несколько лет, когда это было актуально; 1999? Эра VC6, определенно ). Вы можете услышать много болезненных воплей от программистов, которые не прислушались к совету, если вы сделаете небольшую паузу.

Я могу понять побуждение держать его приглушенным, но вы должны признать, что #include <usefulfwd.h> не более болезненна, чем class Useful, а весит . Только это простое делегирование избавит вас от N-1 изменений с class Useful на class useful::Useful.

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

ЦСИ / libuseful / usefulfwd.h

#ifndef GUARD
#define GUARD
namespace useful {
    class Useful;
} // namespace useful
#endif

SRC / MyApp / MyApp-usefulfwd.h

#ifndef GUARD
#define GUARD
#include <usefulfwd.h>
using useful::Useful;
#endif

По сути, это вопрос сохранения кода DRY . Вам может не понравиться броский TLA, но этот описывает действительно основной принцип программирования.

0 голосов
/ 04 февраля 2010

Что ж, правда в том, что нет способа легко избежать запутывания кода в C ++. Однако использование глобального пространства имен - наихудшая идея, потому что тогда у вас нет возможности выбирать между реализациями. Вы получаете объект вместо объекта. Это хорошо работает дома, потому что вы можете редактировать исходный код на ходу, но если кто-то отправляет такой код клиенту, он не должен ожидать, что он будет долгим.

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

0 голосов
/ 04 февраля 2010

нет, вы не увеличиваете связь. Как уже говорили другие - я не вижу, как использование пространства имен пропускает реализацию

потребитель может выбрать

 using useful;
 using useful::Foo;
 useful::Foo = new useful::Foo();

мой голос всегда за последний - наименее загрязняющий

Первый должен быть сильно обескуражен (расстрелом)

0 голосов
/ 04 февраля 2010

Если у вас есть несколько реализаций вашей «полезной» библиотеки, то не исключено ли (если вы не под вашим контролем), что они будут использовать одно и то же пространство имен, будь то global пространство имен или полезное пространство имен?

Другими словами, использование именованного пространства имен по сравнению с глобальным пространством имен не имеет ничего общего с тем, насколько «связаны» вы с библиотекой / реализацией.

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

...