Jaka logika kryje się za słowem kluczowym „using” w C ++?


145

Jaka logika kryje się za słowem kluczowym „using” w C ++?

Jest używany w różnych sytuacjach i staram się sprawdzić, czy wszystkie mają ze sobą coś wspólnego i istnieje powód, dla którego słowo kluczowe „using” jest używane jako takie.

using namespace std; // to import namespace in the current namespace
using T = int; // type alias
using SuperClass::X; // using super class methods in derived class

53
Standardowy komitet nienawidzi wprowadzania nowych słów kluczowych do gramatyki języka C ++.
teh internets jest zrobiony z catz

4
@tehinternetsismadeofcatz Jeśli taka jest logika, proszę wybacz mi, że pójdę i zabiję się teraz.
user3111311

62
@ user3111311: Rozumiesz konsekwencje wprowadzenia nowych zastrzeżonych słów, prawda? Oznacza to, że cały istniejący kod, który używał ich jako nazw identyfikatorów, nagle przestaje się kompilować. To jest ZŁA RZECZ. Na przykład istnieje wiele kodu C, którego nie można skompilować jako C ++, ponieważ zawiera takie elementy, jak int class;. Byłoby jeszcze gorzej, gdyby kod C ++ nagle przestał być prawidłowy w C ++.
Ben Voigt,

7
@BenVoigt: Fakt, że kod w C int class;nie będzie się kompilował jako C ++, nie jest całkowicie zły. Można go użyć do zagwarantowania, że ​​kod C zostanie skompilowany jako C. Zbyt łatwo zapomnieć, że C i C ++ to dwa różne języki - i praktycznie rzecz biorąc, istnieje kod, który jest prawidłowy w C i prawidłowy C ++, ale z inną semantyką.
Keith Thompson

1
Pod tym względem usingnie jest gorszy (ani lepszy) niż static. IMHO kwestia niewprowadzania nowych słów kluczowych jest bardzo ważna, ponieważ wyjaśniono w Internecie, że są to catz i Ben Voigt.
Cassio Neri,

Odpowiedzi:


114

W C ++ 11 usingsłowo kluczowe użyte do type aliasjest identyczne z typedef.

7.1.3.2

Nazwę typedef można również wprowadzić za pomocą deklaracji aliasu. Identyfikator następujący po słowie kluczowym using staje się nazwą typu typedef, a opcjonalna sekwencja specyfikatora atrybutu następująca po identyfikatorze odnosi się do tej nazwy typu. Ma taką samą semantykę, jak gdyby została wprowadzona przez specyfikator typedef. W szczególności nie definiuje nowego typu i nie pojawia się w identyfikatorze typu.

Bjarne Stroustrup podaje praktyczny przykład:

typedef void (*PFD)(double);    // C style typedef to make `PFD` a pointer to a function returning void and accepting double
using PF = void (*)(double);    // `using`-based equivalent of the typedef above
using P = [](double)->void; // using plus suffix return type, syntax error
using P = auto(double)->void // Fixed thanks to DyP

Pre-C ++ 11, usingsłowo kluczowe może przenosić funkcje składowe w zakres. W C ++ 11 możesz teraz zrobić to dla konstruktorów (kolejny przykład Bjarne Stroustrupa):

class Derived : public Base { 
public: 
    using Base::f;    // lift Base's f into Derived's scope -- works in C++98
    void f(char);     // provide a new f 
    void f(int);      // prefer this f to Base::f(int) 

    using Base::Base; // lift Base constructors Derived's scope -- C++11 only
    Derived(char);    // provide a new constructor 
    Derived(int);     // prefer this constructor to Base::Base(int) 
    // ...
}; 

Ben Voight podaje całkiem dobry powód dla uzasadnienia niewprowadzania nowego słowa kluczowego lub nowej składni. Standard chce jak najbardziej uniknąć łamania starego kodu. Dlatego w dokumentach wniosku będzie można zobaczyć odcinki podoba Impact on the Standard, Design decisionsi jak mogą one wpłynąć starszego kodu. Są sytuacje, w których propozycja wydaje się być naprawdę dobrym pomysłem, ale może nie mieć znaczenia, ponieważ byłaby zbyt trudna do wdrożenia, zbyt zagmatwana lub byłaby sprzeczna ze starym kodem.


Oto stara praca z 2003 n1449 . Wydaje się, że uzasadnienie dotyczy szablonów. Ostrzeżenie: mogą wystąpić literówki z powodu kopiowania z pliku PDF.

Najpierw rozważmy przykład zabawki:

template <typename T>
class MyAlloc {/*...*/};

template <typename T, class A>
class MyVector {/*...*/};

template <typename T>

struct Vec {
typedef MyVector<T, MyAlloc<T> > type;
};
Vec<int>::type p; // sample usage

Podstawowym problemem związanym z tym idiomem i głównym faktem motywującym dla tej propozycji jest to, że idiom powoduje, że parametry szablonu pojawiają się w kontekście niemożliwym do wyprowadzenia. Oznacza to, że nie będzie możliwe wywołanie poniższej funkcji foo bez jawnego określenia argumentów szablonu.

template <typename T> void foo (Vec<T>::type&);

Tak więc składnia jest nieco brzydka. Wolelibyśmy uniknąć zagnieżdżenia ::type Wolelibyśmy coś takiego:

template <typename T>
using Vec = MyVector<T, MyAlloc<T> >; //defined in section 2 below
Vec<int> p; // sample usage

Zwróć uwagę, że w szczególności unikamy terminu „typedef template” i wprowadzamy nową składnię obejmującą parę „using” i „=”, aby uniknąć nieporozumień: nie definiujemy tutaj żadnych typów, wprowadzamy synonim (tj. Alias) dla abstrakcja identyfikatora typu (tj. wyrażenie typu) obejmująca parametry szablonu. Jeśli parametry szablonu są używane w kontekstach możliwych do wyprowadzenia w wyrażeniu typu, to za każdym razem, gdy alias szablonu jest używany do utworzenia identyfikatora szablonu, można wydedukować wartości odpowiednich parametrów szablonu - więcej na ten temat będzie dalej. W każdym razie możliwe jest teraz pisanie funkcji ogólnych, które działają Vec<T>w kontekście dającym się wyprowadzić, a także poprawiona została składnia. Na przykład moglibyśmy przepisać foo jako:

template <typename T> void foo (Vec<T>&);

Podkreślamy tutaj, że jednym z głównych powodów proponowania aliasów szablonów było to, aby dedukcja argumentów i wywołanie foo(p) zakończyły się sukcesem.


Kolejny dokument n1489 wyjaśnia, dlaczego usingzamiast używać typedef:

Zaproponowano (ponownie) użycie słowa kluczowego typedef - tak jak to zrobiono w artykule [4] - w celu wprowadzenia aliasów szablonów:

template<class T> 
    typedef std::vector<T, MyAllocator<T> > Vec;

Ta notacja ma tę zaletę, że używa już znanego słowa kluczowego do wprowadzenia aliasu typu. Jednak wyświetla również kilka wad, między innymi zamieszanie związane ze stosowaniem słowa kluczowego, o którym wiadomo, że wprowadza alias dla nazwy typu w kontekście, w którym alias nie oznacza typu, ale szablon; Vecnie jest aliasem dla typu i nie powinien być traktowany jako nazwa typu typedef. Nazwa Vecto nazwa rodziny std::vector< [bullet] , MyAllocator< [bullet] > > - gdzie punktor jest symbolem zastępczym nazwy typu. W związku z tym nie proponujemy składni „typedef”. Z drugiej strony zdanie

template<class T>
    using Vec = std::vector<T, MyAllocator<T> >;

można odczytać / zinterpretować jako: od teraz będę używać Vec<T>jako synonimu dla std::vector<T, MyAllocator<T> >. Po tym odczytaniu nowa składnia aliasingu wydaje się całkiem logiczna.

Myślę, że istnieje tutaj ważne rozróżnienie, alias es zamiast typu s. Inny cytat z tego samego dokumentu:

Deklaracja aliasu jest deklaracją, a nie definicją. Deklaracja aliasu wprowadza nazwę do deklaratywnego regionu jako alias dla typu wyznaczonego przez prawą stronę deklaracji. Sedno tej propozycji dotyczy aliasów nazw typów, ale notację można oczywiście uogólnić, aby zapewnić alternatywną pisownię aliasów przestrzeni nazw lub zestawu nazewnictwa przeciążonych funkcji (dalsze omówienie znajduje się w ✁ 2.3). [ Moja uwaga: ta sekcja omawia, jak może wyglądać ta składnia i powody, dla których nie jest częścią propozycji. ] Można zauważyć, że deklaracja aliasu produkcji gramatyki jest akceptowalna wszędzie tam, gdzie dopuszczalna jest deklaracja typedef lub definicja aliasu przestrzeni nazw.

Podsumowanie, do roli using:

  • aliasy szablonów (lub definicje typów szablonów, pierwszy jest preferowany w odniesieniu do nazwy)
  • aliasy przestrzeni nazw (tj. namespace PO = boost::program_optionsiusing PO = ... równoważne)
  • dokument mówi A typedef declaration can be viewed as a special case of non-template alias-declaration . To zmiana estetyczna iw tym przypadku jest uważana za identyczną.
  • wnoszenie czegoś do zakresu (na przykład namespace stddo zasięgu globalnego), funkcje składowe, dziedziczenie konstruktorów

Nie można go używać do:

int i;
using r = i; // compile-error

Zamiast tego zrób:

using r = decltype(i);

Nazywanie zestawu przeciążeń.

// bring cos into scope
using std::cos;

// invalid syntax
using std::cos(double);

// not allowed, instead use Bjarne Stroustrup function pointer alias example
using test = std::cos(double);

2
@ user3111311 Jakie inne słowo kluczowe masz na myśli? "automatyczny"? "zarejestrować"?
Raymond Chen

2
using P = [](double)->void;to, AFAIK, niepoprawne C ++ 11. To jest jednak: using P = auto(double)->void;i tworzy typ funkcji (taki, który P*jest wskaźnikiem funkcji).
dyp

2
Nazywa się Bjarne Stroustrup;) (zwróć uwagę na drugie r w Stroustrup)
dyp

1
@RaymondChen: właściwie registernie brzmiałoby to tak źle, jest w:register X as Y
MFH

1
Niestety registerzaczyna się deklaracja zmiennej, więc ma to już znaczenie. Zadeklaruj zmienną rejestrową o nazwie Y typu X.
Raymond Chen,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.