Zpět na domů

Using v C++: name lookup a opakované deklarace

Článek rozebírá chování using-direktiv a deklarací v C++: opakované v namespace a blocích, omezení v třídách, rozdíly v name lookup. Uvádějí se příklady kódu s neočekávaným chováním.

Pasti using v C++: proč jména klamou kompilátor
Advertisement 728x90

Použití using-direktiv a deklarací v C++: skryté chytře při hledání jmen

V oborech názvů je povoleno opakované použití stejného jména pomocí using-deklarací, aniž by to způsobilo konflikt. Kompilátor tyto duplikáty ignoruje jako bezvadné opakování.

namespace lib {
    int value = 42;
}

namespace demo {
    using lib::value;
    using lib::value; // správně, žádná nová entita

    void f() {
        value = 10; // lib::value
    }
}

Toto pravidlo se vyvíjelo od C++98, kde byly opakování v blokových oborech formálně zakázány, ale kompilátory (GCC, MSVC) je přijímaly. Issue CWG 36 v C++03 standard upravil, aby se chování shodovalo s chováním v oborech názvů.

Blokové obory: historická nesymetrie

V funkcích nebo blokových oborech způsobovalo opakování using stejného jména dříve chybu při kompilaci podle C++98 §7.3.3/9:

Google AdInline article slot
void f() {
    using A::i;
    using A::i; // chyba v standardu 98
}

Praxe kompilátorů předběhla standard: kód fungoval. Komise uznala rozpor a porovnala to s povolenými opakovanými deklaracemi funkcí:

void f() {
    void g();
    void g(); // platný kód
}

Od C++03 jsou opakování v blokových oborech povoleny, pokud je jméno identické.

Omezení v třídách

Použití using v třídách funguje pouze pro jména z nadřazených tříd, nikoli z vnějších oborů názvů:

Google AdInline article slot
struct S {
    using lib::value; // chyba: nečlenské jméno v oboru třídy
};

To je zásadní omezení: using-deklarace nepřenáší vnější jména přímo do třídy.

Rozdíl mezi using namespace a using-deklarací

Klíčový rozdíl spočívá v mechanismu hledání jmen:

  • using namespace rozšiřuje hledání, ale nezavádí jména do aktuálního oboru.
  • using-deklarace deklaruje jméno lokálně v daném oboru.

Příklad ukazuje konflikt:

Google AdInline article slot
namespace B { int x = 10; }
namespace C { int x = 20; }
namespace A {
    using namespace B; // direktiva: rozšiřuje hledání

    void f() {
        using C::x; // deklarace: zavádí x lokálně

        A::x = 1; // B::x (kvalifikované hledání prostřednictvím using namespace)
        x = 2;    // C::x (nezkvalifikované, lokální jméno)
    }
}

Ve funkci f() dvě řádky mění různé proměnné: A::x (což je B::x) a lokální x (C::x). Nezkvalifikované hledání se zastaví na prvním shodném jménu v oboru.

Úplný test:

int main() {
    A::f();
    printf("A::x: %d\n", A::x); // 1
    printf("B::x: %d\n", B::x); // 1
    printf("C::x: %d\n", C::x); // 2
}

Praktické důsledky pro revize kódu

Takové konstrukce komplikují pochopení kódu. V velkých funkcích mohou using-deklarace zakrýt skutečné zdroje jmen a vést ke chybám během provozu. Doporučení: vyhnout se using v produkčním kódu nebo jejich použití jasně dokumentovat.

Na pohovorech 80 % středně pokročilých a senior programátorů nedokáže vysvětlit chování bez ladění — indikátor nedostatku zkušeností s hledáním jmen.

Důležité:

  • Opakované using jednoho jména jsou povoleny v oborech názvů i blokových oborech od C++03.
  • Using v třídách je možné pouze z nadřazených tříd.
  • using namespace rozšiřuje hledání, ale nezavádí jména; using-deklarace zavádí lokální jméno.
  • Nezkvalifikované hledání se zastaví na prvním shodném jménu v oboru.
  • Takové vzory jsou červeným vlajkem při revizi kódu.

— Editorial Team

Advertisement 728x90

Číst dál