Чому “використання простору імен X;” заборонено на рівні класу / структури?


88
class C {
  using namespace std;  // error
};
namespace N {
  using namespace std; // ok
}
int main () {
  using namespace std; // ok
}

Редагувати : Хочете знати мотивацію за цим.


1
@pst: C # не має нічого подібного using namespace. C # дозволяє щось подібне, але лише в обсязі файлу. C ++ using namespaceдозволяє включити один простір імен в інший.
Billy ONeal

2
Дублікат цього питання ?
greatwolf

@ZachSaw, я розумію вашу стурбованість. Спробували закрити Qn виходячи з релевантності. Оскільки ця публікація містить більш об’єктивну відповідь та посилання на стандарт, я тримав її відкритою. Раніше багато моїх старших Qn закривали новіші Qn .. іноді мною, іноді іншими. Будь ласка, повідомте про алмазні моди, якщо ви вважаєте, що це рішення було недоречним. Немає образ. :-)
iammilind 02.03.17

@iammilind не міг дбати про TBH. ТАК у ці дні безлад. Але позначення допису, який починається з "Не знаю точно", оскільки відповідь насправді містить "більш об'єктивну відповідь та посилання на стандарт". Ха-ха.
Zach Saw

@ZachSaw, я говорив не просто про прийняту відповідь, а про загальну публікацію. Так, це об’єктивно, але стандартна цитата міститься у цій відповіді . Починається з "я не знаю", оскільки навіть у стандартному режимі не виправдано, чому "використання простору імен" заборонено всередині class/struct. Це просто не дозволяється. Але прийнята відповідь обговорює цілком логічне обгрунтування, щоб заборонити її. тобто де розглянути Hello::Worldі де розглянути World. Надія, яка знімає сумніви.
iammilind

Відповіді:


35

Я точно не знаю, але я здогадуюсь, що дозволяючи це на рівні класу може спричинити плутанину:

namespace Hello
{
    typedef int World;
}

class Blah
{
    using namespace Hello;
public:
    World DoSomething();
}

//Should this be just World or Hello::World ?
World Blah::DoSomething()
{
    //Is the using namespace valid in here?
}

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

Тепер причина цього менш заплутана, коли ми говоримо про сфери імен:

namespace Hello
{
    typedef int World;
}

namespace Other
{
    using namespace Hello;
    World DoSomething();
}

//We are outside of any namespace, so we have to fully qualify everything. Therefore either of these are correct:

//Hello was imported into Other, so everything that was in Hello is also in Other. Therefore this is okay:
Other::World Other::DoSomething()
{
    //We're outside of a namespace; obviously the using namespace doesn't apply here.
    //EDIT: Apparently I was wrong about that... see comments. 
}

//The original type was Hello::World, so this is okay too.
Hello::World Other::DoSomething()
{
    //Ditto
}

namespace Other
{
    //namespace Hello has been imported into Other, and we are inside Other, so therefore we never need to qualify anything from Hello.
    //Therefore this is unambiguiously right
    World DoSomething()
    {
        //We're inside the namespace, obviously the using namespace does apply here.
    }
}

5
+1, я думав з цієї причини, але тоді те саме застосовується і using namespace Hello;всередині іншого namespaceexternфункції оголошення всередині нього).
iammilind

10
Я не думаю, що це бентежить. C ++ - це не здогади. Якби це було дозволено, тоді комітет ISO C ++ зазначив би в специфікації мови. Тоді ви б не сказали, що це заплутано. В іншому випадку можна сказати, що навіть це бентежить: ideone.com/npOeD ... але тоді правило для такого кодування вказане в специфікації.
Nawaz

1
@Nawaz: Більшість користувачів мови. Я ніколи не казав, що C ++ стосується здогадок. Я кажу, що коли специфікація розроблена, вона розроблена з такою поведінкою, яку більшість програмістів очікуватиме раніше часу. А правила на папері часто є заплутаним - стандартні спроби бути однозначним , але це не завжди вдається.
Billy ONeal

6
У першому прикладі це повинно бути: Hello::World Blah::DoSomething()або Blah::World Blah::DoSomething()(якщо це було дозволено), тип повернення визначення функції-члена не вважається таким, що входить до сфери дії класу в мові, тому його слід кваліфікувати. Розглянемо дійсний приклад заміни usingа typedef Hello::World World;на область обсягу класу at. Тож сюрпризів там бути не повинно.
Девід Родрігес - dribeas

2
Якби це було дозволено, я вважаю, що це застосовувалось би на лексичному рівні. Я думаю, що це "очевидне" рішення, яке практично не має жодних здивувань.
Томас Едінг,

18

Оскільки стандарт С ++ це явно забороняє. З C ++ 03 §7.3.4 [namespace.udir]:

директива using :
    використання простору імен :: opt  вкладеного імені-специфікатора opt  namespace-name ;

З допомогою директиви не повинна з'являтися в області видимості класу, але може з'явитися в області видимості простору імен або в блоці області. [Примітка: під час пошуку імені простору імен у директиві using враховуються лише імена простору імен, див. 3.4.6. ]

Чому стандарт С ++ це забороняє? Не знаю, запитайте члена комітету ISO, який затвердив мовний стандарт.


45
Ще одна технічно правильна, але марна відповідь; найгірший вид. 1) відповідь знає більше людей, ніж просто комітет. 2) члени комітетів беруть участь у SO 3) якщо ви не знаєте відповіді (враховуючи дух питання), навіщо взагалі відповідати?
Catskul

5
@Catskul: це не марна відповідь. Дуже корисно знати, що стандарт прямо передбачає це і забороняє. Також іронічно, що найбільш прихильна відповідь починається з "я не знаю точно". Крім того, "стандарт забороняє" це не те саме, що "це не дозволено, тому що компілятор не дозволяє", оскільки останній випадок не відповів би на наступні запитання на зразок: це проблема з моїм компілятором? компілятор не відповідає стандартам? це побічний ефект деяких інших речей, про які я не знаю? тощо
antonone

9

Я вважаю, що обгрунтування полягає в тому, що це могло б заплутати. В даний час під час обробки ідентифікатора рівня класу пошук спочатку здійснюватиме пошук у області класу, а потім у просторі імен, що вмикає. Дозвіл на using namespaceрівні класу матиме досить побічні ефекти щодо того, як зараз виконується пошук. Зокрема, це повинно було б бути виконано десь між перевіркою цього конкретного обсягу класу та перевіркою укладеного простору імен. Тобто: 1) об’єднати пошуки рівня класу та використовуваного простору імен, 2) шукати використаний простір імен після сфери дії класу, але перед будь-яким іншим обсягом класу, 3) шукати використаний простір імен безпосередньо перед укладеним простором імен. 4) пошук, об’єднаний із закритим простором імен.

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

.

namespace A {
   void foo() {}
   struct B {
      struct foo {};
      void f() {
         foo();      // value initialize a A::B::foo object (current behavior)
      }
   };
}
struct C {
   using namespace A;
   struct foo {};
   void f() {
      foo();         // call A::foo
   }
};
  1. Шукати відразу після цього класу. Це мало би дивний ефект від заслідування членів базових класів. Поточний пошук не поєднує пошук класу та простору імен, і під час пошуку класу він перейде до базових класів, перш ніж розглядати укладений простір імен. Така поведінка була б дивною, оскільки вона не розглядала б простір імен на рівні, подібному до простору імен, що вмикається. Знову ж таки, використаний простір імен матиме пріоритет над укладеним простором імен.

.

namespace A {
   void foo() {}
}
void bar() {}
struct base {
   void foo();
   void bar();
};
struct test : base {
   using namespace A;
   void f() {
      foo();           // A::foo()
      bar();           // base::bar()
   }
};
  1. Пошук безпосередньо перед закритим простором імен. Проблема такого підходу знову полягає в тому, що це було б для багатьох дивним. Врахуйте, що простір імен визначено в іншій одиниці перекладу, так що такий код неможливо побачити відразу:

.

namespace A {
   void foo( int ) { std::cout << "int"; }
}
void foo( double ) { std::cout << "double"; }
struct test {
   using namespace A;
   void f() {
      foo( 5.0 );          // would print "int" if A is checked *before* the
                           // enclosing namespace
   }
};
  1. Злиття з укладеним простором імен. Це мало б такий самий ефект, як застосування usingоголошення на рівні простору імен. Це не додало б до цього жодного нового значення, але, з іншого боку, ускладнило б пошук для реалізаторів компілятора. Пошук ідентифікатора простору імен тепер не залежить від того, де в коді ініціюється пошук. Коли всередині класу, якщо пошук не знаходить ідентифікатор в області дії класу, він повернеться до пошуку простору імен, але це точно той самий пошук простору імен, який використовується у визначенні функції, немає необхідності підтримувати новий стан. Коли usingоголошення знайдено на рівні простору імен, вміст використовуваного простору імен переноситься в цей простір імен для всіх пошуків, що включають простір імен. Якщоusing namespace було дозволено на рівні класу, існували б різні результати для пошуку простору імен того самого простору імен, залежно від того, звідки було запущено пошук, і це зробило б здійснення пошуку набагато складнішим без додаткового значення.

У будь-якому випадку, моя рекомендація - взагалі не застосовувати using namespaceдекларацію. Це робить код простішим для міркувань, не маючи на увазі вміст усіх просторів імен.


1
Я згоден, що використання, як правило, створює неявні дивацтва. Але деякі бібліотеки можуть бути розроблені з урахуванням того, що usingіснує. Помірно оголошуючи речі в глибоко вкладених довгих просторах імен. Наприклад, glmце робить і використовує кілька прийомів для активації / представлення функцій, коли клієнт використовує using.
v.oddou

навіть прямо в STL using namespace std::placeholders. cf en.cppreference.com/w/cpp/utility/functional/bind
v.oddou

@ v.oddou:namespace ph = std::placeholders;
Девід Родрігес - dribeas

1

Це, мабуть, заборонено через відкритість чи закритість.

  • Класи та структури в C ++ - це завжди закриті сутності. Вони визначені рівно в одному місці (хоча ви можете розділити декларацію та реалізацію).
  • простори імен можна часто відкривати, повторно відкривати та розширювати довільно.

Імпорт просторів імен у класи призвів би до таких смішних випадків:

namespace Foo {}

struct Bar { using namespace Foo; };

namespace Foo {
using Baz = int; // I've just extended `Bar` with a type alias!
void baz(); // I've just extended `Bar` with what looks like a static function!
// etc.
}

0

Я думаю, що це дефект мови. Ви можете використати обхідне рішення нижче. Маючи на увазі це обхідне рішення, легко запропонувати правила вирішення конфліктів імен для випадку, коли мова буде змінена.

namespace Hello
{
    typedef int World;
}
// surround the class (where we want to use namespace Hello)
// by auxiliary namespace (but don't use anonymous namespaces in h-files)
namespace Blah_namesp {
using namespace Hello;

class Blah
{
public:
    World DoSomething1();
    World DoSomething2();
    World DoSomething3();
};

World Blah::DoSomething1()
{
}

} // namespace Blah_namesp

// "extract" class from auxiliary namespace
using Blah_namesp::Blah;

Hello::World Blah::DoSomething2()
{
}
auto Blah::DoSomething3() -> World
{
}

Чи можете ви додати якесь пояснення?
Kishan Bharda

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