Для чого використовуються вбудовані простори імен?


334

C ++ 11 дозволяє inline namespaces, всі члени яких також автоматично знаходяться в додатку namespace. Я не можу придумати жодного корисного застосування цього - може хтось, будь-ласка, навести короткий, стислий приклад ситуації, коли inline namespaceпотрібна ситуація, і де це найбільш ідіоматичне рішення?

(Крім того, мені незрозуміло, що відбувається, коли a namespaceоголошується inlineв одній, але не у всіх деклараціях, які можуть міститись у різних файлах. Чи це не клопотання про проблеми?)

Відповіді:


339

Вбудовані простори імен - це функція версії бібліотеки, схожа на версію символів , але реалізована суто на рівні C ++ 11 (тобто крос-платформа) замість того, щоб бути особливістю певного бінарного виконуваного формату (тобто для платформи).

Це механізм, за допомогою якого автор бібліотеки може зробити вкладений простір імен і виглядати так, ніби всі його декларації знаходились у навколишньому просторі імен (вбудовані простори імен можуть бути вкладені, тому "більш вкладені" імена переповнюються аж до першого -введіть простір імен і подивіться та дійте так, ніби їхні декларації були і в одному з просторів імен між ними).

Як приклад, розглянемо реалізацію STL для vector. Якби у нас були вбудовані простори імен з початку C ++, то в C ++ 98 заголовок, <vector>можливо, виглядав би так:

namespace std {

#if __cplusplus < 1997L // pre-standard C++
    inline
#endif

    namespace pre_cxx_1997 {
        template <class T> __vector_impl; // implementation class
        template <class T> // e.g. w/o allocator argument
        class vector : __vector_impl<T> { // private inheritance
            // ...
        };
    }
#if __cplusplus >= 1997L // C++98/03 or later
                         // (ifdef'ed out b/c it probably uses new language
                         // features that a pre-C++98 compiler would choke on)
#  if __cplusplus == 1997L // C++98/03
    inline
#  endif

    namespace cxx_1997 {

        // std::vector now has an allocator argument
        template <class T, class Alloc=std::allocator<T> >
        class vector : pre_cxx_1997::__vector_impl<T> { // the old impl is still good
            // ...
        };

        // and vector<bool> is special:
        template <class Alloc=std::allocator<bool> >
        class vector<bool> {
            // ...
        };

    };

#endif // C++98/03 or later

} // namespace std

Залежно від значення вибирається __cplusplusабо одна, або інша vectorреалізація. Якщо кодовий була написано в попередньо C ++ 98 разів, і ви виявите , що C ++ 98 версії vectorвикликають проблеми для вас , коли ви оновити ваш компілятор, «все» ви повинні зробити , це знайти посилання на std::vectorв вашу кодову базу та замініть їх на std::pre_cxx_1997::vector.

Приходьте на наступний стандарт, а STL продавець просто повторює процедуру знову, вводячи новий простір імен для std::vectorз emplace_backпідтримкою (що вимагає C ++ 11) і вбудовування , що один IFF __cplusplus == 201103L.

Гаразд, чому для цього мені потрібна нова мовна функція? Я вже можу зробити наступне, щоб мати такий же ефект, ні?

namespace std {

    namespace pre_cxx_1997 {
        // ...
    }
#if __cplusplus < 1997L // pre-standard C++
    using namespace pre_cxx_1997;
#endif

#if __cplusplus >= 1997L // C++98/03 or later
                         // (ifdef'ed out b/c it probably uses new language
                         // features that a pre-C++98 compiler would choke on)

    namespace cxx_1997 {
        // ...
    };
#  if __cplusplus == 1997L // C++98/03
    using namespace cxx_1997;
#  endif

#endif // C++98/03 or later

} // namespace std

Залежно від значення __cplusplus, я отримую або одну, або іншу реалізації.

І ти був би майже прав.

Розглянемо наступний дійсний код користувача C ++ 98 (було дозволено повністю спеціалізувати шаблони, які вже існують у просторі імен stdна C ++ 98):

// I don't trust my STL vendor to do this optimisation, so force these 
// specializations myself:
namespace std {
    template <>
    class vector<MyType> : my_special_vector<MyType> {
        // ...
    };
    template <>
    class vector<MyOtherType> : my_special_vector<MyOtherType> {
        // ...
    };
    // ...etc...
} // namespace std

Це абсолютно дійсний код, коли користувач надає власну реалізацію вектора для набору типів, де вона, мабуть, знає більш ефективну реалізацію, ніж та, яка знайдена в (її копії) STL.

Але : спеціалізуючись на шаблоні, вам потрібно зробити це в просторі імен, про яке було оголошено. Стандарт говорить, що vectorоголошено в просторі імен std, тому користувач справедливо розраховує спеціалізувати тип.

Цей код працює з неперевершеним простором імен stdабо з функцією просторового простору імен C ++ 11, але не з використовуваним трюком версії using namespace <nested>, оскільки це розкриває деталі реалізації, що справжній простір імен, в якому vectorбуло визначено, не був stdбезпосередньо.

Є й інші дірки, за допомогою яких можна було виявити вкладений простір імен (див. Коментарі нижче), але вбудовані простори імен підключають їх усі. І це все є. Неймовірно корисно для майбутнього, але Стандарт AFAIK не призначає вбудовані імена простору імен для власної стандартної бібліотеки (хоча б хотілося, щоб у цьому помилялися), тому його можна використовувати лише для сторонніх бібліотек, не сам стандарт (якщо виробники компілятора не погоджуються щодо схеми імен).


23
+1 для пояснення того, чому using namespace V99;не працює на прикладі Stroustrup.
Стів Джессоп

3
І так само, якщо я запускаю абсолютно нову реалізацію C ++ 21 з нуля, то я не хочу обтяжуватись реалізацією багатьох старих дурниць std::cxx_11. Не кожен компілятор завжди буде реалізовувати всі старі версії стандартних бібліотек, хоча на даний момент спокусливо думати, що вимагати залишок існуючих реалізацій залишатимуться в старих, коли вони додають нові, було б дуже мало тягаря, оскільки насправді вони всі все одно. Я думаю, що те, що стандарт міг би бути корисним, зробить його необов’язковим, але зі стандартною назвою, якщо він є.
Стів Джессоп

46
Це ще не все. ADL також була причиною (ADL не буде дотримуватися використання директив), і пошук імен теж. ( using namespace Aу просторі імен B створює імена в просторі імен B приховує імена в просторі імен A, якщо ви шукаєте B::name- не так, як вбудовані простори імен).
Йоханнес Шауб - ліб

4
Чому б просто не використовувати ifdefs для повноцінної реалізації вектора? Усі реалізації будуть в одному просторі імен, але лише одна з них буде визначена після попередньої обробки
sasha.sochka

6
@ sasha.sochka, тому що в цьому випадку ви не можете використовувати інші реалізації. Вони будуть видалені препроцесором. За допомогою вбудованих просторів імен ви можете використовувати будь-яку необхідну реалізацію, вказавши повністю кваліфіковане ім’я (або usingключове слово).
Василь Бірюков

70

http://www.stroustrup.com/C++11FAQ.html#inline-namespace (документ, написаний та підтримуваний Bjarne Stroustrup, який, на вашу думку, повинен знати про мотивацію більшості функцій C ++ 11. )

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

Наведений приклад:

// file V99.h:
inline namespace V99 {
    void f(int);    // does something better than the V98 version
    void f(double); // new feature
    // ...
}

// file V98.h:
namespace V98 {
    void f(int);    // does something
    // ...
}

// file Mine.h:
namespace Mine {
#include "V99.h"
#include "V98.h"
}

#include "Mine.h"
using namespace Mine;
// ...
V98::f(1);  // old version
V99::f(1);  // new version
f(1);       // default version

Я не відразу розумію, чому ви не вводите using namespace V99;всередину простору імен Mine, але мені не потрібно цілком розуміти випадок використання, щоб прийняти слово Бьярне на це на мотивацію комітету.


Тож насправді остання f(1)версія буде викликана з вбудованого V99простору імен?
Ейтан Т

1
@EitanT: так, оскільки глобальний простір імен має using namespace Mine;, а Mineпростір імен містить усе з простого простору імен Mine::V99.
Стів Джессоп

2
@Walter: ви видаляєте inlineз файлу V99.hреліз, який включає V100.h. Ви також можете одночасно модифікувати Mine.h, звичайно, щоб додати додаткове включення. Mine.hє частиною бібліотеки, а не частиною коду клієнта.
Стів Джессоп

5
@walter: вони не встановлюються V100.h, вони встановлюють бібліотеку під назвою "Шахта". Є 3 файлу заголовка в версії 99 «Mine» - Mine.h, V98.hі V99.h. Є 4 файлів заголовка в версії 100 «Mine» - Mine.h, V98.h, V99.hі V100.h. Розташування файлів заголовків - це детальна інформація про реалізацію, яка не має значення для користувачів. Якщо вони виявлять певну проблему сумісності, що означає, що їм потрібно використовувати конкретно Mine::V98::fякийсь або весь свій код, вони можуть змішувати дзвінки Mine::V98::fзі старого коду з дзвінками до Mine::fново написаного коду.
Стів Джессоп

2
@Walter Як згадується інша відповідь, шаблони повинні бути спеціалізовані на просторі імен, в яких вони оголошені, а не на просторі імен, використовуючи той, про який вони заявлені. Mine, замість того, щоб спеціалізуватися на Mine::V99або Mine::V98.
Час Джастіна - Відновіть Моніку

8

Окрім усіх інших відповідей.

Вбудований простір імен може використовуватися для кодування інформації ABI або Версії функцій у символах. Саме з цієї причини вони використовуються для забезпечення відсталої сумісності з ABI. Вбудовані простори імен дозволяють вводити інформацію в кероване ім'я (ABI), не змінюючи API, оскільки вони впливають лише на ім'я символу лінкера.

Розглянемо цей приклад:

Припустимо, ви пишете функцію, Fooяка посилається на об'єкт, скажіть, barі нічого не повертає.

Скажіть у main.cpp

struct bar;
void Foo(bar& ref);

Якщо ви перевіряєте своє ім'я символу для цього файлу після його компіляції в об’єкт.

$ nm main.o
T__ Z1fooRK6bar 

Ім'я символу лінкера може відрізнятися, але воно обов'язково десь закодує ім'я типів функцій та аргументів.

Тепер це може бути barвизначено як:

struct bar{
   int x;
#ifndef NDEBUG
   int y;
#endif
};

Залежно від типу складання, barможе посилатися на два різні типи / макети з однаковими символами лінкера.

Щоб запобігти такій поведінці, ми загортаємо структуру barв вбудовану область імен, де залежно від типу збірки символ лінкера barбуде різним.

Отже, ми могли б написати:

#ifndef NDEBUG
inline namespace rel { 
#else
inline namespace dbg {
#endif
struct bar{
   int x;
#ifndef NDEBUG
   int y;
#endif
};
}

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

$ nm rel.o
T__ ZROKfoo9relEbar
$ nm dbg.o
T__ ZROKfoo9dbgEbar

Назви символів лінкера можуть бути різними.

Помітьте наявність relі dbgв назвах символів.

Тепер, якщо ви спробуєте зв’язати налагодження з режимом випуску або навпаки, ви отримаєте помилку Linker, що суперечить помилці виконання.


1
Так, це має сенс. Тож це більше для реалізаторів бібліотеки тощо.
Вальтер

3

Я фактично виявив ще одне використання для вбудованих просторів імен.

За допомогою Qt ви отримуєте кілька додаткових приємних функцій, використовуючи Q_ENUM_NS, що, в свою чергу, вимагає, щоб у просторі імен, що вкладається, містився метаоб'єкт, який оголошено с Q_NAMESPACE. Однак, Q_ENUM_NSщоб працювати, Q_NAMESPACE у цьому ж файлі має бути відповідне corresponding. І може бути лише одна, або ви отримуєте повторювані помилки визначення. Це фактично означає, що всі ваші перерахування повинні бути в одному заголовку. Гидота.

Або ... ви можете використовувати вбудовані простори імен. Приховування перерахувань уinline namespaceпричинах призводить до того, що мета-об'єкти мають різні зловмисні імена, при цьому шукаючи користувачів, як-от додаткового простору імен не існує⁽²⁾.

Таким чином, вони корисні для поділу матеріалу на кілька суб-просторів імен , які все виглядають як один простір імен, якщо вам потрібно зробити , що з деяких причин. Звичайно, це схоже на написання using namespace innerу зовнішньому просторі імен, але без порушення DRY написання імені внутрішнього простору імен двічі.


  1. Це насправді гірше за це; він повинен бути в одному наборі брекетів.

  2. Якщо ви не намагаєтеся отримати доступ до мета-об'єкта без повної його кваліфікації, але метаоб'єкт навряд чи використовується безпосередньо.


Чи можете ви намалювати це скелетом коду? (в ідеалі без чіткого посилання на Qt). Все це звучить досить втягнуто / неясно.
Вальтер

Не ... легко. Причина, що потрібні окремі простори імен, пов'язана з деталями реалізації Qt. TBH, важко уявити ситуацію поза Qt, яка мала б однакові вимоги. Однак для цього Qt-сценарію вони корисні! Для прикладу дивіться gist.github.com/mwoehlke-kitware/… або github.com/Kitware/seal-tk/pull/45 .
Метью

0

Отже, підсумовуючи основні моменти, using namespace v99і inline namespaceвони не були однаковими, перше було вирішенням бібліотек версій до введення спеціального ключового слова (вбудованого тексту) в C ++ 11, яке вирішило проблеми використання using, забезпечуючи однакову функціональність версій. Використання, що using namespaceвикористовується для створення проблем з ADL (хоча ADL зараз, як видається, дотримується usingдиректив), і позалінійна спеціалізація класу / функції бібліотеки тощо користувачем не буде працювати, якщо це зробити за межами справжнього простору імен (чиє ім'я користувач не повинен і не повинен знати, тобто користувачеві доведеться використовувати B :: abi_v2 ::, а не просто B :: для вирішення спеціалізації).

//library code
namespace B { //library name the user knows
    namespace A { //ABI version the user doesn't know about
        template<class T> class myclass{int a;};
    }
    using namespace A; //pre inline-namespace versioning trick
} 

// user code
namespace B { //user thinks the library uses this namespace
    template<> class myclass<int> {};
}

Це покаже попередження про статичний аналіз first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]. Але якщо зробити простір імен A inline, компілятор правильно вирішує спеціалізацію. Хоча із розширенням на C ++ 11 проблема не входить.

Позастатеві визначення не використовуються під час використання using; вони повинні бути оголошені у вкладеному / не вкладеному блоку простору імен розширень (це означає, що користувачеві потрібно знову знати версію ABI, якщо з будь-якої причини їм було дозволено надавати власну реалізацію функції).

#include <iostream>
namespace A {
    namespace B{
        int a;
        int func(int a);
        template<class T> class myclass{int a;};
        class C;
        extern int d;
    } 
    using namespace B;
} 
int A::d = 3; //No member named 'd' in namespace A
class A::C {int a;}; //no class named 'C' in namespace 'A' 
template<> class A::myclass<int> {}; // works; specialisation is not an out-of-line definition of a declaration
int A::func(int a){return a;}; //out-of-line definition of 'func' does not match any declaration in namespace 'A'
namespace A { int func(int a){return a;};} //works
int main() {
    A::a =1; // works; not an out-of-line definition
}

Питання зникає, коли B стає в рядку.

Інші inlineпростори імен функціональних можливостей дозволяють бібліотечному письменнику забезпечувати прозоре оновлення бібліотеки 1) без примушування користувача перетворювати код рефактора з новим іменем простору імен; 2) запобігати відсутності багатослівності та 3) надання абстракції деталей, що не стосуються API, 4) надання такої ж корисної діагностики лінкера та поведінки, що і використання не просто вбудованого простору імен. Скажімо, ви використовуєте бібліотеку:

namespace library {
    inline namespace abi_v1 {
        class foo {
        } 
    }
}

Це дозволяє користувачеві телефонувати library::fooбез необхідності знати або включати версію ABI в документацію, яка виглядає більш чистою. Використання library::abiverison129389123::fooвиглядатиме брудно.

Коли буде зроблено оновлення foo, тобто додавання нового члена до класу, це не вплине на існуючі програми на рівні API, оскільки вони вже не будуть використовувати член І зміна вбудованого імені простору імен нічого не змінить на рівні API бо library::fooвсе одно буде працювати.

namespace library {
    inline namespace abi_v2 {
        class foo {
            //new member
        } 
    }
}

Однак для програм, які посилаються на нього, оскільки ім'я вбудованого простору імен перетворюється на імена символів, як звичайна область імен, зміна не буде прозорою для посилання. Тому, якщо додаток не перекомпільовано, а пов'язане з новою версією бібліотеки, воно подасть символ, який abi_v1не знайдено помилки, а не він фактично посилається, а потім викликає таємничу логічну помилку під час виконання через несумісність ABI. Додавання нового члена спричинить сумісність ABI через зміну визначення типу, навіть якщо це не впливає на програму під час компіляції (рівень API).

У цьому сценарії:

namespace library {
    namespace abi_v1 {
        class foo {
        } 
    }

    inline namespace abi_v2 {
        class foo {
            //new member
        } 
    }
}

Як і використання двох просторів імен, які не є вбудованими, це дозволяє приєднати нову версію бібліотеки без необхідності перекомпілювати додаток, оскільки abi_v1він буде перетворений в один із глобальних символів і буде використовувати правильне (старе) визначення типу. Однак перекомпіляція програми призведе до вирішення посилань library::abi_v2.

Використання using namespaceє менш функціональним, ніж використання inline(у тому, що визначення поза рядками не вирішується), але дає ті самі 4 переваги, що і вище. Але справжнє питання полягає в тому, навіщо продовжувати використовувати вирішення, коли зараз для цього є виділене ключове слово. Це краща практика, менш докладна мова (доведеться змінити 1 рядок коду замість 2) і робить це зрозумілим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.