Правильне використання просторів імен

Переклад статті Using Namespaces Properly від Dejan Jelovic

Простори імен – дуже потужна особливість мови C++. Ця стаття не вчитиме синтаксису просторів імен, вона просто покаже як їх варто використовувати.

Простори імен лише обгортають всі імена, що в нього входять в якесь інше ім’я. Наприклад:

namespace net
  {
  class Socket { ... };
  }

...

net::Socket socket;

Завдяки цьому ми впевнюємося, що якщо дві бібліотеки матимуть свої реалізації класу Socket, то якщо вони назвуть свої простори імен по-різному, ваша програма зможе їх використовувати без яких би то не було конфліктів.

Але це викликає ще одне питання: якщо дві незалежні компанії вирішать писати мережеві бібліотеки, то який шанс, що вони будуть робити реалізацію класу з назвою Socket? Я так думаю, що вірогідність цього відсотків 100.

Нам також подобається, коли простори імен легко набираються, що означає, що вони мають бути довжиною в 2-4 символи. З урахуванням цього, який шанс, що обидві компанії назвуть свій простір імен “Net”? 5 відсотків? 10 відсотків?

Як би там не було, це показує, що простори імен не вирішують цю проблему повністю – вони просто зменшують вірогідність її виникнення.

Рішення промислового рівня

Розв’язком цієї проблеми є використання довгих, унікальних назв простору імен, а потім їх включення в програму під коротким псевдонімом (alias).

Тож компанія, що розробляє мережеву бібліотеку напише щось типу:

namespace net_33843894
   {
   class Socket { ... };
   }

де номер після “net_” має бути згенеровано генератором випадкових чисел. Нехай цей код розміщено в файлі заголовків з назвою.

Далі бібліотека продається клієнту, який вирішує використати її в проекті. Клієнт тоді описує свій власний спеціалізований для проекту файл заголовків з назвою типу і наступним вмістом:

#include <netlib>

namespace net = net_33843894;

Щойно ми створили власний псевдонім для простору імен для бібліотеки, який діє лише в межах нашого проекту. Якби простір імен “net” вже було зайнято іншою бібліотекою, то ми могли б обрати інше ім’я: net2, sock, чи ще щось. Тоді не виникало б ніяких конфліктів імен.

Спрощуючи все

Розумним було б зробити з бібліотекою щось таке, щоб вона стала простішою для користувача. В ідеалі він мав би клацнути двічі на інсталяційному файлі і бібліотека негайно стала б доступною у його середовищі розробки (IDE). Далі він просто набирав би “#include ” і використовував її, щоб створити щось корисне.

Але якщо користувачу доведеться створювати свій заголовок для кожного заголовку вашої бібліотеки, то доведеться постійно страждати, використовуючи її. Не кожному це буде до смаку.

Розв’язком цієї проблеми є забезпечення розумних значень по замовчуванню, але дозволивши йому можливість змінити їх, якщо вони не підійдуть. Це можна зробити за допомогою макродиректив в файлі заголовків:

namespace net_33843894
  {
  class Socket { ... };
  }
  
#ifndef NO_NET_33843894_ALIAS
  namespace net = net_33843894;
#endif

Таким способом можна надати логічне значення по замовчуванню для простору імен, а якщо воно вже зайняте, то користувач зможе введенням макросу NO_NET_33843894_ALIAS уникнути створення псевдоніму “net”.

Сучасні компілятори

Повідомлення про помилки це справжній кошмар при використанні шаблонів. З довгими назвами просторів імен ми зробимо їх ще жахливішими.

На жаль, жоден з компіляторів, якими я користуюсь, недостатньо розумний, щоб використовувати найкоротший з доступних псевдонімів у повідомленнях про помилки. Тобто навіть коли ви використовуєте аліас “net”, то повідомлення про помилку при використанні класу Socket буде посилатись на net_33843894::Socket. Що не дуже то читабельно.

Тому я зазвичай йду на маленьку хитрість. Вона працює лише в заголовках, що містять виключно inline-функції (оскільки вона діє лиш на актуальні імена, що використовуються лінковщиком), але в мене їх достобіса. Якщо макрос NO_NET_33843894_ALIAS не визначено, то я використовую коротке ім’я як ім’я простору імен, а довге – як псевдонім:

#ifdef NO_NET_33843894_ALIAS
namespace net_33843894
  {
#else
namespace net
  {
#endif

  class Socket { ... };
  }
  
#ifndef NO_NET_33843894_ALIAS
  namespace net_33843894 = net;
#endif

І повідомлення про помилки стають більш зрозумілими.