Цей урок є продовженням уроку №27.
Конфлікт імен
Конфлікт імен виникає, коли два ідентифікатори знаходяться в одній області видимості, і компілятор не може зрозуміти, який з них двох слід використовувати в конкретній ситуації. Компілятор або лінкер видасть вам помилку, оскільки у нього недостатньо інформації, щоб вирішити цю неоднозначність. Як тільки програми збільшуються в об’ємах, збільшується кількість ідентифікаторів, отже, ймовірність виникнення конфліктів імен збільшується також.
Розглянемо приклад такого конфлікту. boo.h і doo.h — це заголовки з функціями, які виконують різні речі, але мають однакові імена та параметри.
boo.h:
1 2 3 4 5 |
// Функція doOperation() виконує операцію додавання своїх параметрів int doOperation(int a, int b) { return a + b; } |
doo.h:
1 2 3 4 5 |
// Функція doOperation() виконує операцію віднімання своїх параметрів int doOperation(int a, int b) { return a - b; } |
main.cpp:
1 2 3 4 5 6 7 8 9 |
#include <iostream> #include "boo.h" #include "doo.h" int main() { std::cout << doOperation(5, 4); // яка версія функції doOperation() виконається тут? return 0; } |
Якщо boo.h і doo.h скомпілювати окремо, то все пройде без інцидентів. Однак, з’єднавши їх в одній програмі, ми підключимо дві різні функції, але з однаковими іменами і параметрами, в одну область видимості (глобальну), а це, в свою чергу, призведе до конфлікту імен. В результаті, компілятор видасть помилку. Ось для вирішення таких от проблем і додали в мову С++ таку концепцію, як простір імен.
Що таке простір імен?
Простір імен визначає область коду, в якій гарантується унікальність всіх ідентифікаторів. За замовчуванням, глобальні змінні і звичайні функції визначені в глобальному просторі імен. Наприклад:
1 2 3 4 5 6 |
int g_z = 4; int boo(int z) { return -z; } |
Глобальна змінна g_z
і функція boo() визначені в глобальному просторі імен.
У вищенаведеному прикладі при підключенні файлів boo.h і doo.h, обидві версії функції doOperation() були додані в глобальний простір імен, через що і, власне, стався конфлікт імен.
Щоб уникнути подібних ситуацій, коли два незалежних об’єкти мають ідентифікатори, які можуть конфліктувати один з одним при спільному використанні, мова C++ дозволяє оголошувати власні простори імен через ключове слово namespace. Все, що оголошено всередині користувацького простору імен, належить тільки цьому простору імен (а не глобальному).
Перепишемо заголовкові файли з вищенаведеного прикладу, але вже з використанням простору імен:
boo.h:
1 2 3 4 5 6 7 8 |
namespace Boo { // Ця версія функції doOperation() належить простору імен Boo int doOperation(int a, int b) { return a + b; } } |
doo.h:
1 2 3 4 5 6 7 8 |
namespace Doo { // Ця версія функції doOperation() належить простору імен Doo int doOperation(int a, int b) { return a - b; } } |
Тепер функція doOperation() з файлу boo.h знаходиться в просторі імен Boo
, а функція doOperation() з файлу doo.h — в просторі імен Doo
. Подивимося, що станеться при перекомпіляції main.cpp:
1 2 3 4 5 |
int main() { std::cout << doOperation(5, 4); // яка версія функції doOperation() виконається тут? return 0; } |
Результатом буде ще одна помилка:
C:\VCProjects\Test.cpp(15) : error C2065: 'doOperation' : undeclared identifier
Сталося так, що коли ми спробували викликати функцію doOperation(), компілятор заглянув в глобальний простір імен в пошуках визначення doOperation(). Однак, оскільки жодна з наших версій doOperation() не перебуває у глобальному просторі імен, компілятор просто не зміг знайти визначення doOperation() взагалі!
Існує два різні способи повідомити компілятору, яку версію doOperation() слід використовувати: через оператор дозволу області видимості або за допомогою using-стейтментів (про них ми поговоримо на наступному уроці).
Доступ до простору імен через оператор дозволу області видимості (::)
Перший спосіб вказати компілятору шукати ідентифікатор в певному просторі імен — це використати назву необхідного простору імен разом з оператором дозволу області видимості (::
) і необхідним ідентифікатором.
Наприклад, повідомимо компілятору використати версію doOperation() з простору імен Boo
:
1 2 3 4 5 |
int main(void) { std::cout << Boo::doOperation(5, 4); return 0; } |
Результат:
9
Якщо б ми захотіли використати версію doOperation() з простору імен Doo
:
1 2 3 4 5 |
int main(void) { std::cout << Doo::doOperation(5, 4); return 0; } |
Результат:
1
Оператор дозволу області видимості дозволяє вибрати конкретний простір імен. Ми навіть можемо зробити наступне:
1 2 3 4 5 6 |
int main(void) { std::cout << Boo::doOperation(5, 4) << '\n'; std::cout << Doo::doOperation(5, 4) << '\n'; return 0; } |
Результат:
9
1
Також цей оператор можна використовувати без будь-якого префіксу (наприклад, ::doOperation
). В такому випадку ми посилаємося на глобальний простір імен.
Простори імен з однаковими іменами
Допускається оголошення просторів імен в декількох місцях (або в декількох файлах, або в декількох місцях усередині одного файлу). Все, що знаходиться всередині одного блоку імен, вважається частиною тільки цього блоку.
add.h:
1 2 3 4 5 6 7 8 |
namespace DoMath { // Функція add() є частиною простору імен DoMath int add(int x, int y) { return x + y; } } |
subtract.h:
1 2 3 4 5 6 7 8 |
namespace DoMath { // Функція subtract() є частиною простору імен DoMath int subtract(int x, int y) { return x - y; } } |
main.cpp:
1 2 3 4 5 6 7 8 9 10 |
#include "add.h" // імпортуємо DoMath::add() #include "subtract.h" // імпортуємо DoMath::subtract() int main(void) { std::cout << DoMath::add(5, 4) << '\n'; std::cout << DoMath::subtract(5, 4) << '\n'; return 0; } |
Все працює, як потрібно.
Стандартна бібліотека C++ широко використовує цю особливість, оскільки всі заголовки, які знаходяться в ній, реалізовують свій функціонал всередині простору імен std
.
Псевдоніми і вкладені простори імен
Одні простори імен можуть бути вкладені в інші простори імен. Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> namespace Boo { namespace Doo { const int g_x = 7; } } int main() { std::cout << Boo::Doo::g_x; return 0; } |
Зверніть увагу, оскільки Doo
знаходиться всередині Boo
, то доступ до g_x
здійснюється через Boo::Doo::g_x
.
Оскільки це не завжди зручно і ефективно, то мова C++ дозволяє створювати псевдоніми для просторів імен:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#include <iostream> namespace Boo { namespace Doo { const int g_x = 7; } } namespace Foo = Boo::Doo; // Foo тепер рахується як Boo::Doo int main() { std::cout << Foo::g_x; // це, насправді, Boo::Doo::g_x return 0; } |
Варто відзначити, що простори імен в мові C++ не були розроблені як спосіб реалізації інформаційної ієрархії — вони були розроблені як механізм запобігання виникненню конфліктів імен. Як доказ цьому, вся Стандартна бібліотека шаблонів знаходиться в єдиному просторі імен std::
.
Вкладеність просторів імен не рекомендується використовувати, тому що при невмілому використанні збільшується ймовірність виникнення помилок і додатково ускладнюється логіка програми.