Dlaczego „używać przestrzeni nazw X;” nie jest dozwolone na poziomie klasy / struktury?

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

Edycja : chcesz poznać motywację za tym.

iammilind
źródło
1
@pst: C # nie ma nic podobnego using namespace. C # pozwala na coś podobnego, ale tylko w zakresie pliku. C ++ using namespacepozwala na włączenie jednej przestrzeni nazw do innej.
Billy ONeal
2
Duplikat tego pytania ?
greatwolf
@ZachSaw, rozumiem twoje obawy. Próbowałem zamknąć Qn na podstawie trafności. Ponieważ ten post zawiera bardziej obiektywną odpowiedź i odniesienie do standardu, pozostawiłem go otwartym. W przeszłości wiele moich starszych Qn zostało zamkniętych przez nowszy Qn .. czasami przeze mnie, czasami przez innych. Jeśli uważasz, że ta decyzja nie była właściwa, zgłoś się do Diamond Mods. Bez urazy. :-)
iammilind
@iammilind nie obchodziło mniej TBH. TAK jest w dzisiejszych czasach bałagan. Ale oznaczenie postu zaczynającego się od „Nie wiem dokładnie” jako odpowiedź naprawdę zawiera „bardziej obiektywną odpowiedź i odniesienie do standardu”. Ha ha.
Zach Saw
@ZachSaw, nie mówiłem tylko o zaakceptowanej odpowiedzi, ale o ogólnym poście. Tak, to obiektywne, ale w tej odpowiedzi znajduje się standardowy cytat . Zaczyna się od „Nie wiem”, ponieważ nawet w standardzie nie jest uzasadnione, dlaczego „używanie przestrzeni nazw” jest niedozwolone w środku class/struct. To po prostu niedozwolone. Ale przyjęta odpowiedź zawiera bardzo logiczne uzasadnienie, aby tego zabronić. tj. gdzie wziąć pod uwagę Hello::Worldi gdzie wziąć pod uwagę World. Mam nadzieję, że to rozwiąże wątpliwości.
iammilind

Odpowiedzi:

36

Nie wiem dokładnie, ale przypuszczam, że zezwolenie na to w zakresie klasy może spowodować zamieszanie:

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?
}

Ponieważ nie ma oczywistego sposobu, aby to zrobić, standard mówi tylko, że nie możesz.

Teraz powód, dla którego jest to mniej zagmatwane, gdy mówimy o zakresach przestrzeni nazw:

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.
    }
}
Billy ONeal
źródło
5
+1, pomyślałem o tym powodzie, ale to samo ma zastosowanie również do using namespace Hello;wewnątrz innych namespace(i deklarowania externfunkcji w nim).
iammilind
10
Nie sądzę, żeby to było mylące. C ++ nie polega na zgadywaniu. Gdyby było to dozwolone, komitet C ++ ISO określiłby to w specyfikacji języka. Wtedy nie powiedziałbyś, że to zagmatwane. W przeciwnym razie można by powiedzieć, że nawet to jest mylące: ideone.com/npOeD ... ale wtedy reguła takiego kodowania jest określona w specyfikacji.
Nawaz
1
@Nawaz: Większość użytkowników tego języka. Nigdy nie powiedziałem, że C ++ dotyczy zgadywania. Mówię, że kiedy specyfikacja jest zaprojektowana, jest zaprojektowana z zachowaniem, jakiego większość programistów spodziewa się z wyprzedzeniem. A zasady na papierze często mylące - standardowe starają się być jednoznaczne, ale nie zawsze się to udaje.
Billy ONeal
6
W pierwszym przykładzie powinno to być: Hello::World Blah::DoSomething()lub Blah::World Blah::DoSomething()(jeśli było to dozwolone), zwracany typ definicji funkcji składowej nie jest uważany za znajdujący się w zakresie klasy w języku, więc musi być kwalifikowany. Rozważmy ważny przykład zastępując usingz typedef Hello::World World;w zakresie klasy. Więc nie powinno być tam żadnych niespodzianek.
David Rodríguez - dribeas
2
Gdyby było to dozwolone, uważam, że byłoby stosowane na poziomie zakresu leksykalnego. Myślę, że jest to „oczywiste” rozwiązanie praktycznie bez niespodzianek.
Thomas Eding
19

Ponieważ standard C ++ wyraźnie tego zabrania. Z C ++ 03 §7.3.4 [namespace.udir]:

using-dyrektywa :
    używając przestrzeni nazw :: opt  nested-name-specifier opt  namespace-name ;

Użyciu dyrektywą nie powinny pojawiać się w zakresie klasy, ale może pojawić się w zakresie przestrzeni nazw lub w zakresie bloku. [Uwaga: podczas wyszukiwania nazwy przestrzeni nazw w dyrektywie using, brane są pod uwagę tylko nazwy przestrzeni nazw, patrz 3.4.6. ]

Dlaczego standard C ++ tego zabrania? Nie wiem, zapytaj członka komitetu ISO, który zatwierdził standard językowy.

Adam Rosenfield
źródło
46
Jeszcze jedna poprawna technicznie, ale bezużyteczna odpowiedź; najgorszy rodzaj. 1) więcej osób niż tylko komisja zna odpowiedź. 2) członkowie komitetu uczestniczą w SO 3) jeśli nie znasz odpowiedzi (biorąc pod uwagę ducha pytania), po co w ogóle odpowiadać?
Catskul
6
@ Catskul: to nie jest bezużyteczna odpowiedź. Warto wiedzieć, że norma wyraźnie to dotyczy i zabrania tego. To także ironia losu, że najbardziej pozytywna odpowiedź zaczyna się od „Nie wiem dokładnie”. Ponadto „standard zabrania tego” to nie to samo, co „nie jest dozwolone, ponieważ kompilator na to nie zezwala”, ponieważ ten drugi przypadek nie odpowiada na dodatkowe pytania, takie jak: czy to problem z moim kompilatorem? czy kompilator nie jest zgodny ze standardami? czy jest to efekt uboczny innych rzeczy, o których nie wiem? itp.
antonone
9

Uważam, że uzasadnienie jest takie, że prawdopodobnie byłoby to zagmatwane. Obecnie, podczas przetwarzania identyfikatora poziomu klasy, wyszukiwanie będzie najpierw przeszukiwać zakres klasy, a następnie otaczającą przestrzeń nazw. Zezwolenie na to using namespacena poziomie klasy miałoby pewne skutki uboczne na sposób przeprowadzania wyszukiwania. W szczególności należałoby to wykonać gdzieś pomiędzy sprawdzeniem tego konkretnego zakresu klasy a sprawdzeniem otaczającej przestrzeni nazw. Czyli: 1) scal poziom klasy i zastosowane wyszukiwania na poziomie przestrzeni nazw, 2) wyszukaj używaną przestrzeń nazw po zakresie klasy, ale przed jakimkolwiek innym zakresem klas, 3) wyszukaj używaną przestrzeń nazw tuż przed otaczającą przestrzenią nazw. 4) wyszukiwanie scalone z otaczającą przestrzenią nazw.

  1. To duża różnica, gdzie identyfikator na poziomie klasy byłoby cienia jakiegokolwiek identyfikatora w przestrzeni nazw zamykającego, ale to nie będzie śledzony jest używany nazw. Efekt byłby dziwny, ponieważ dostęp do używanej przestrzeni nazw z klasy w innej przestrzeni nazw iz tej samej przestrzeni nazw byłby inny:

.

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. Wyszukaj bezpośrednio po tym zakresie klasy. Miałoby to dziwny efekt zacieniania członków klas bazowych. Bieżące wyszukiwanie nie łączy wyszukiwań na poziomie klas i przestrzeni nazw, a podczas wyszukiwania klas przejdzie do klas podstawowych przed rozważeniem otaczającej przestrzeni nazw. Zachowanie byłoby zaskakujące, ponieważ nie uwzględniałoby przestrzeni nazw na podobnym poziomie do otaczającej przestrzeni nazw. Ponownie, używana przestrzeń nazw będzie miała wyższy priorytet niż otaczająca przestrzeń nazw.

.

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. Wyszukaj tuż przed otaczającą przestrzenią nazw. Problem z tym podejściem polega ponownie na tym, że dla wielu byłoby to zaskakujące. Weź pod uwagę, że przestrzeń nazw jest zdefiniowana w innej jednostce tłumaczeniowej, aby poniższy kod nie był widoczny od razu:

.

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. Scal z otaczającą przestrzenią nazw. Miałoby to dokładnie taki sam efekt, jak zastosowanie usingdeklaracji na poziomie przestrzeni nazw. Nie dodałoby to żadnej nowej wartości, ale z drugiej strony skomplikuje wyszukiwanie implementatorów kompilatora. Wyszukiwanie identyfikatorów przestrzeni nazw jest teraz niezależne od tego, gdzie w kodzie jest uruchamiane wyszukiwanie. Jeśli w klasie funkcja lookup nie znajdzie identyfikatora w zakresie klasy, powróci do wyszukiwania w przestrzeni nazw, ale jest to dokładnie to samo wyszukiwanie przestrzeni nazw, które jest używane w definicji funkcji, nie ma potrzeby utrzymywania nowego stanu. Gdy usingdeklaracja zostanie znaleziona na poziomie przestrzeni nazw, zawartość używanej przestrzeni nazw jest przenoszona do tej przestrzeni nazw dla wszystkich wyszukiwań obejmujących przestrzeń nazw. Jeśliusing namespace było dozwolone na poziomie klasy, wyniki wyszukiwania przestrzeni nazw dokładnie tej samej przestrzeni nazw byłyby różne w zależności od tego, skąd zostało uruchomione wyszukiwanie, a to sprawiłoby, że implementacja wyszukiwania byłaby znacznie bardziej złożona bez dodatkowej wartości.

W każdym razie zalecam, aby w ogóle nie stosować using namespacedeklaracji. Ułatwia to rozumowanie kodu bez konieczności pamiętania o zawartości wszystkich przestrzeni nazw.

David Rodríguez - dribeas
źródło
1
Zgadzam się, że używanie ma tendencję do tworzenia ukrytych osobliwości. Ale niektóre biblioteki mogą być zaprojektowane wokół istniejącego faktu using. Celowo deklarując rzeczy w głęboko zagnieżdżonych długich przestrzeniach nazw. Np. glmRobi to i używa wielu sztuczek, aby aktywować / prezentować funkcje, gdy klient używa using.
v.oddou,
nawet bezpośrednio w STL using namespace std::placeholders. cf en.cppreference.com/w/cpp/utility/functional/bind
v.oddou
@ v.oddou:namespace ph = std::placeholders;
David Rodríguez - dribeas
1

Jest to prawdopodobnie niedozwolone ze względu na otwartość kontra zamknięcie.

  • Klasy i struktury w C ++ są zawsze zamkniętymi jednostkami. Są zdefiniowane dokładnie w jednym miejscu (chociaż można podzielić deklarację i implementację).
  • przestrzenie nazw mogą być otwierane, ponownie otwierane i rozszerzane w dowolny sposób.

Importowanie przestrzeni nazw do klas doprowadziłoby do takich śmiesznych przypadków:

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.
}
DanielS
źródło
Lub moglibyśmy po prostu NIE definiować członków klasy za pomocą importowanych nazw. Niech ta konstrukcja doda się namespace Foodo kolejności wyszukiwania dla całego kodu wewnątrz definicji typu struct Bar, podobnie jak umieszczenie tego wiersza w każdej treści funkcji składowej wbudowanej, z tą różnicą, że byłby również aktywny dla inicjatorów z nawiasami klamrowymi lub równymi itp. wygasają w nawiasie zamykającym, tak samo jak using namespacew treści funkcji elementu członkowskiego. Teraz niestety wydaje się, że nie ma sposobu na użycie wyszukiwania Koenig-with-fallback w inicjatorze nawiasów klamrowych lub równych bez zanieczyszczania otaczającej przestrzeni nazw.
Ben Voigt
0

Myślę, że to wada języka. Możesz zastosować obejście poniżej. Mając na uwadze to obejście, łatwo jest zasugerować zasady rozwiązywania konfliktów nazw w przypadku zmiany języka.

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
{
}
naprimeroleg
źródło
Czy możesz dodać wyjaśnienie?
Kishan Bharda,
Tak, dodałem kilka komentarzy
naprimeroleg