Perché "using namespace X;" non è consentito all'interno del livello di classe / struttura?


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

Modifica : vuoi conoscere la motivazione dietro di esso.


1
@pst: C # non ha nulla di simile using namespace. C # consente qualcosa di simile, ma solo nell'ambito del file. Il C ++ using namespaceti permette di incorporare uno spazio dei nomi in un altro.
Billy ONeal

2
Duplicato di questa domanda ?
lupo mannaro

@ ZachSaw, capisco la tua preoccupazione. Ho provato a chiudere il Qn in base alla rilevanza. Poiché questo post contiene risposte e riferimenti più oggettivi allo standard, l'ho tenuto aperto. In passato, molti dei miei vecchi Qn sono stati chiusi da nuovi Qn .. a volte da me a volte da altri. Si prega di segnalare ai Mod diamante, se ritieni che questa decisione non fosse appropriata. Senza rancore. :-)
iammilind

@iammilind non potrebbe importare di meno TBH. COSÌ è un casino in questi giorni. Ma contrassegnare un post che inizia con "Non so esattamente" come risposta contiene in realtà "risposta più oggettiva e riferimento allo standard". Haha.
Zach Saw

@ ZachSaw, non stavo parlando solo della risposta accettata, ma del post generale. Sì, è oggettivo ma la citazione standard è contenuta in questa risposta . Inizia con "Non lo so", perché anche in standard, non è giustificato il motivo per cui "usare lo spazio dei nomi" non è consentito all'interno class/struct. Semplicemente non è permesso. Ma la risposta accettata discute una logica molto logica per rifiutarla. vale a dire dove considerare Hello::Worlde dove considerare World. Spero che cancelli il dubbio.
iammilind

Risposte:


36

Non lo so esattamente, ma la mia ipotesi è che consentire questo nell'ambito della classe potrebbe causare confusione:

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

Poiché non esiste un modo ovvio per farlo, lo standard dice semplicemente che non puoi.

Ora, il motivo per cui questo crea meno confusione quando parliamo di ambiti dello spazio dei nomi:

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, ho pensato a questo motivo, ma poi la stessa cosa è applicabile anche using namespace Hello;all'interno di altri namespace(e dichiarando la externfunzione al suo interno).
iammilind

10
Non penso che sia fonte di confusione. Il C ++ non riguarda le supposizioni. Se fosse consentito, il comitato C ++ ISO avrebbe specificato nella specifica del linguaggio. Allora non diresti che confonde. Altrimenti si potrebbe dire che anche questo è fonte di confusione: ideone.com/npOeD ... ma poi la regola per tale codifica è specificata nelle specifiche.
Nawaz

1
@Nawaz: la maggior parte degli utenti della lingua. Non ho mai detto che il C ++ riguardasse le supposizioni. Sto dicendo che quando la specifica è progettata, è progettata con il comportamento che la maggior parte dei programmatori si aspetterà prima del tempo. E le regole sulla carta spesso sono confuso - i tentativi standard per essere inequivocabile, ma non sempre riesce.
Billy ONeal

6
Nel primo esempio, dovrebbe essere: Hello::World Blah::DoSomething()o Blah::World Blah::DoSomething()(se consentito), il tipo restituito di una definizione di funzione membro non è considerato nell'ambito della classe nella lingua, quindi deve essere qualificato. Considera il valido esempio di sostituzione di usingcon un typedef Hello::World World;ambito di classe. Quindi non dovrebbero esserci sorprese lì.
David Rodríguez - dribeas

2
Se fosse consentito, credo che sarebbe applicato a livello di ambito lessicale. Penso che questa sia la soluzione "ovvia" praticamente senza sorprese.
Thomas Eding

19

Perché lo standard C ++ lo proibisce esplicitamente. Da C ++ 03 §7.3.4 [namespace.udir]:

using-Directive :
    using namespace :: opt  specificatore-nome-annidato opt  namespace-name ;

Una direttiva using non deve apparire nell'ambito della classe, ma può apparire nell'ambito dello spazio dei nomi o nell'ambito del blocco. [Nota: quando si cerca un nome-spazio dei nomi in una direttiva using, vengono considerati solo i nomi degli spazi dei nomi, vedere 3.4.6. ]

Perché lo standard C ++ lo vieta? Non lo so, chiedi a un membro del comitato ISO che ha approvato lo standard linguistico.


48
Ancora un'altra risposta tecnicamente corretta ma inutile; il peggior tipo. 1) più persone oltre al comitato conoscono la risposta. 2) i membri del comitato partecipano all'SO 3) se non conosci la risposta (visto lo spirito della domanda) perché rispondere?
Catskul

7
@ Catkul: non è una risposta inutile. È molto utile sapere che lo standard affronta esplicitamente questo problema e lo vieta. È anche ironico che la risposta più votata inizi con "Non so esattamente". Inoltre, lo "standard lo vieta" non è lo stesso di "non è consentito perché il compilatore non lo consente", perché quest'ultimo caso non risponderebbe a domande successive come: è un problema con il mio compilatore? il compilatore non è conforme agli standard? è un effetto collaterale di altre cose di cui non sono a conoscenza? ecc.
antonone

9

Credo che la logica sia che probabilmente creerebbe confusione. Attualmente, durante l'elaborazione di un identificatore a livello di classe, la ricerca cercherà prima nell'ambito della classe e poi nello spazio dei nomi che lo racchiude. Consentire il using namespacelivello di classe avrebbe alcuni effetti collaterali sul modo in cui viene eseguita la ricerca. In particolare, dovrebbe essere eseguito a volte tra il controllo di quel particolare ambito di classe e il controllo dello spazio dei nomi che lo racchiude. Ovvero: 1) unire le ricerche a livello di classe e quelle a livello di spazio dei nomi utilizzato, 2) cercare lo spazio dei nomi utilizzato dopo l'ambito della classe ma prima di qualsiasi altro ambito di classe, 3) cercare lo spazio dei nomi utilizzato subito prima dello spazio dei nomi che lo racchiude. 4) ricerca fusa con lo spazio dei nomi che lo racchiude.

  1. Ciò farebbe una grande differenza, dove un identificatore a livello di classe oscurerebbe qualsiasi identificatore nello spazio dei nomi che lo racchiude, ma non oscurerebbe uno spazio dei nomi utilizzato . L'effetto sarebbe strano, in quanto l'accesso allo spazio dei nomi utilizzato da una classe in uno spazio dei nomi diverso e dallo stesso spazio dei nomi sarebbe diverso:

.

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. Cerca subito dopo questo ambito di classe. Ciò avrebbe lo strano effetto di nascondere i membri delle classi base. La ricerca corrente non combina ricerche a livello di classe e spazio dei nomi e quando si esegue la ricerca di classi andrà fino alle classi di base prima di considerare lo spazio dei nomi che lo racchiude. Il comportamento sarebbe sorprendente in quanto non considererebbe lo spazio dei nomi a un livello simile allo spazio dei nomi che lo racchiude. Anche in questo caso, lo spazio dei nomi utilizzato sarebbe prioritario rispetto allo spazio dei nomi che lo racchiude.

.

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. Cerca subito prima dello spazio dei nomi che lo racchiude. Il problema con questo approccio è ancora una volta che sarebbe sorprendente per molti. Considera che lo spazio dei nomi è definito in un'unità di traduzione diversa, in modo che il codice seguente non possa essere visto tutto in una volta:

.

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. Unisci con lo spazio dei nomi che lo racchiude. Ciò avrebbe lo stesso identico effetto dell'applicazione della usingdichiarazione a livello di spazio dei nomi. Non aggiungerebbe alcun nuovo valore a ciò, ma d'altro canto complicherà la ricerca per gli implementatori del compilatore. La ricerca dell'identificatore dello spazio dei nomi è ora indipendente da dove viene attivata la ricerca nel codice. Quando si è all'interno di una classe, se la ricerca non trova l'identificatore nell'ambito della classe, tornerà alla ricerca nello spazio dei nomi, ma questa è esattamente la stessa ricerca nello spazio dei nomi utilizzata nella definizione di una funzione, non è necessario mantenere il nuovo stato. Quando la usingdichiarazione si trova a livello di spazio dei nomi, il contenuto dello spazio dei nomi utilizzato viene portato in quello spazio dei nomi per tutti ricerche che coinvolgono lo spazio dei nomi. Seusing namespace era consentito a livello di classe, ci sarebbero risultati diversi per la ricerca nello spazio dei nomi dello stesso identico spazio dei nomi a seconda di dove è stata attivata la ricerca, e ciò renderebbe l'implementazione della ricerca molto più complessa senza alcun valore aggiuntivo.

Ad ogni modo, la mia raccomandazione è di non utilizzare affatto la using namespacedichiarazione. Rende il codice più semplice da ragionare senza dover tenere a mente tutti i contenuti degli spazi dei nomi.


1
Sono d'accordo che l'uso tende a creare stranezze implicite. Ma alcune librerie possono essere progettate in base al fatto che usingesiste. Dichiarando intenzionalmente le cose in spazi dei nomi lunghi e annidati. Ad esempio, lo glmfa e utilizza più trucchi per attivare / presentare le funzionalità quando il client lo utilizza using.
v.oddou

anche proprio nel 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

Questo probabilmente non è consentito a causa dell'apertura e della chiusura.

  • Le classi e le strutture in C ++ sono sempre entità chiuse. Sono definiti esattamente in un punto (sebbene sia possibile dividere dichiarazione e implementazione).
  • gli spazi dei nomi possono essere aperti, riaperti ed estesi arbitrariamente spesso.

L'importazione di spazi dei nomi nelle classi porterebbe a casi divertenti come questo:

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

Oppure potremmo semplicemente NON definire i membri della classe con i nomi importati. Lascia che questo costrutto si aggiunga namespace Fooall'ordine di ricerca per tutto il codice all'interno della definizione del tipo di struct Bar, proprio come inserire quella riga in ogni corpo della funzione membro inline, tranne per il fatto che sarebbe attivo anche per gli inizializzatori parentesi graffe o uguali, ecc. scadono alla parentesi graffa di chiusura, come using namespaceall'interno del corpo della funzione di un membro. Ora sfortunatamente non sembra esserci alcun modo per utilizzare Koenig-with-fallback lookup in un inizializzatore di parentesi graffe o uguali senza inquinare lo spazio dei nomi che lo racchiude.
Ben Voigt

0

Penso che sia un difetto della lingua. È possibile utilizzare la soluzione alternativa di seguito. Tenendo presente questa soluzione alternativa, è facile suggerire regole per la risoluzione dei conflitti di nomi per il caso in cui la lingua verrà modificata.

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

Puoi aggiungere qualche spiegazione?
Kishan Bharda

Sì, ho aggiunto alcuni commenti
naprimeroleg
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.