2.8-Nimeämistörmäykset ja johdatus nimiavaruuksiin

oletetaan, että olet ensimmäistä kertaa ajamassa ystävän taloon ja sinulle annettu osoite on 245 Front Street Mill Cityssä. Kun saavut Mill Cityyn, vedät karttasi esiin, vain huomataksesi, että Mill Cityssä on todellisuudessa kaksi eri Etukatua kaupungin toisella puolella! Mihin niistä menisit? Ellei sitten ollut jotain ylimääräistä johtolankaa, joka auttaisi sinua päättämään (esim. muistat hänen talonsa on lähellä jokea) sinun pitäisi soittaa ystävällesi ja kysyä lisätietoja. Koska tämä olisi hämmentävää ja tehotonta (erityisesti postinkantajalle), useimmissa maissa kaikkien kaupungin kadunnimien ja kotiosoitteiden on oltava ainutlaatuisia.

vastaavasti C++ edellyttää, että kaikki tunnisteet ovat epäselviä. Jos samaan ohjelmaan tuodaan kaksi samanlaista tunnistetta siten, että kääntäjä tai linkittäjä ei pysty erottamaan niitä toisistaan, Kääntäjä tai linkittäjä tuottaa virheen. Tätä virhettä kutsutaan yleensä nimeämiskolariksi (tai nimeämiskonfliktiksi).

esimerkki nimikkokolarista

a.cpp:

1
2
3
4
5
6

#include <iostream>
void myFcn(int x)
{
std:: kout << x;
}

main.cpp:

1
2
3
4
5
6
7
8
9
10
11

#include <iostream>
void myFcn(int x)
{
std:: kout << 2 * x;
}
int main()
{
paluu 0;
}

kun Kääntäjä kokoaa tämän ohjelman, se kokoaa a.cpp ja main.CPP itsenäisesti, ja jokainen tiedosto kääntää ilman ongelmia.

kuitenkin, kun linker suorittaa, se yhdistää kaikki määritelmät a.cpp ja main.CPP yhdessä, ja löytää ristiriitaisia määritelmiä funktio myFcn. Linkkaaja keskeyttää tämän jälkeen virheen vuoksi. Huomaa, että tämä virhe tapahtuu, vaikka myFcn: ää ei koskaan kutsuta!

useimmat nimeämistörmäykset tapahtuvat kahdessa tapauksessa:
1) kaksi (tai useampaa) funktion (tai globaalin muuttujan) määritelmää tuodaan erillisiin tiedostoihin, jotka kootaan samaan ohjelmaan. Tämä johtaa linker virhe, kuten edellä.
2) samaan tiedostoon lisätään kaksi (tai useampia) funktion (tai kokonaismuuttujan) määritelmää (usein #include). Tämä johtaa kääntäjävirheeseen.

kun ohjelmat laajenevat ja käyttävät enemmän tunnisteita, nimitörmäyksen todennäköisyys kasvaa merkittävästi. Hyvä uutinen on, että C++ tarjoaa runsaasti mekanismeja välttää nimeäminen törmäykset. Local scope, joka estää funktioiden sisällä määriteltyjä paikallisia muuttujia ristiriidasta keskenään, on yksi tällainen mekanismi. Mutta paikallinen laajuus ei toimi funktioiden nimille. Miten siis estämme funktioiden nimien ristiriitaisuuden keskenään?

mikä on nimiavaruus?

Takaisin osoitteeseemme hetkeksi, kahden Etukadun pitäminen oli ongelmallista vain siksi, että ne kadut olivat olemassa saman kaupungin sisällä. Toisaalta, jos sinun pitäisi toimittaa postia kahteen osoitteeseen, toinen osoitteeseen 209 Front Street Mill Cityssä ja toinen osoitteeseen 417 Front Street Jonesvillessä, ei olisi epäselvyyttä siitä, minne mennä. Toisin sanoen, kaupungit tarjoavat ryhmittymiä, joiden avulla voimme disambiguate osoitteet, jotka muuten saattavat olla ristiriidassa keskenään. Nimiavaruudet toimivat tässä analogiassa kaupunkien tavoin.

nimiavaruus on alue, jonka sisällä voi ilmoittaa nimiä disambiguaatiota varten. Nimiavaruus antaa soveltamisalan (kutsutaan nimiavaruuden soveltamisalaksi) sen sisällä ilmoitetuille nimille — mikä tarkoittaa yksinkertaisesti sitä, että nimiavaruuden sisällä ilmoitettuja nimiä ei sekoiteta samanlaisiin nimiin muissa laajuuksissa.

Key insight

nimiavaruudessa ilmoitettua nimeä ei voi sekoittaa toisessa laajuudessa ilmoitettuun samannimiseen nimeen.

nimiavaruuden sisällä kaikkien nimien on oltava uniikkeja, muuten tuloksena on nimeämistörmäys.

nimiavaruuksia käytetään usein ryhmittelemään toisiinsa liittyviä tunnisteita suuressa projektissa sen varmistamiseksi, etteivät ne vahingossa törmää muihin tunnisteisiin. Jos esimerkiksi laitat kaikki matematiikkafunktiosi nimiavaruuteen nimeltä math, matematiikkafunktiosi eivät törmää identtisesti nimettyihin funktioihin nimiavaruuden ulkopuolella.

kerromme tulevaisuuden oppitunnilla, miten voit luoda omia nimiavaruuksia.

yleinen nimiavaruus

C++: ssa mikä tahansa nimi, jota ei ole määritelty luokan, funktion tai nimiavaruuden sisällä, katsotaan osaksi implisiittisesti määriteltyä nimiavaruutta, jota kutsutaan maailmanlaajuiseksi nimiavaruudeksi (joskus myös yleiseksi nimiavaruudeksi).

oppitunnin yläosassa olevassa esimerkissä funktiot main() ja myfcn: n molemmat versiot () on määritelty globaalin nimiavaruuden sisällä. Esimerkissä kohdattu nimeämistörmäys johtuu siitä, että myFcn: n() molemmat versiot päätyvät globaalin nimiavaruuden sisään, mikä rikkoo sääntöä, jonka mukaan nimiavaruuden kaikkien nimien on oltava uniikkeja.

std-nimiavaruus

kun c++ suunniteltiin alun perin, kaikki C++ – standardikirjaston tunnisteet (mukaan lukien std::cin ja std::cout) olivat käytettävissä ilman std:: – etuliitettä (ne olivat osa maailmanlaajuista nimiavaruutta). Tämä kuitenkin tarkoitti sitä, että mikä tahansa standardikirjaston tunniste voi olla ristiriidassa minkä tahansa omiin tunnisteisiin valitsemasi nimen kanssa (määritelty myös maailmanlaajuisessa nimiavaruudessa). Toimivassa koodissa saattaa yhtäkkiä olla nimeämisriita, kun #sisällytti uuden tiedoston standardikirjastosta. Tai mikä pahempaa, ohjelmat, jotka kääntyisivät C++: n yhden version alle, eivät välttämättä kääntyisi C++: n tulevan version alle, sillä standardikirjastoon tuoduilla uusilla tunnisteilla voi olla nimeämisriita jo kirjoitetun koodin kanssa. Niinpä C++ siirsi kaikki standardikirjaston toiminnot nimiavaruuteen nimeltä ” std ” (lyhenne standardista).

käy ilmi, että std:: coutin nimi ei oikeasti ole std:: cout. Se on itse asiassa vain kout, ja std on sen nimiavaruuden nimi, johon tunnistekout kuuluu. Koska kout on määritelty std-nimiavaruudessa, nimi kout ei ole ristiriidassa minkään kout-nimisen objektin tai funktion kanssa, jonka luomme maailmanlaajuiseen nimiavaruuteen.

vastaavasti, kun haet nimiavaruudessa (esim.std::cout) määriteltyä tunnistetta , sinun on kerrottava kääntäjälle, että etsimme nimiavaruudessa (std) määriteltyä tunnistetta.

Key insight

kun käytät nimiavaruuden sisällä määriteltyä tunnistetta (kuten std-nimiavaruutta), sinun on kerrottava kääntäjälle, että tunniste elää nimiavaruuden sisällä.

on olemassa muutamia eri tapoja tehdä tämä.

eksplisiittinen nimiavaruuden määrittely std:::

yksinkertaisin tapa kertoa kääntäjälle, että haluamme käyttää koutia std-nimiavaruudesta, on käyttää eksplisiittisesti STD:: – etuliitettä. Esimerkiksi:

1
2
3
4
5
6
7

#include <iostream>
int main()
{
std:: kout << ” Hello world!”; // kun sanomme kout, tarkoitamme STD-nimiavaruudessa
määriteltyä koutia 0;
}

::- symboli on operaattori, jota kutsutaan scope resolution operatoriksi. Symbolin vasemmalla puolella oleva tunniste ilmaisee nimiavaruuden, johon symbolin oikealla puolella oleva nimi sisältyy. Jos:: – symbolin vasemmalla puolella ei ole tunnistetta, oletetaan globaali nimiavaruus.

joten kun sanomme std:: cout, sanomme ”the cout that lives in namespace std”.

tämä on turvallisin tapa käyttää koutia, koska ei ole epäselvyyttä siitä, mihin koutiin viittaamme (se, joka on std-nimiavaruudessa).

Best practice

käytä nimenomaista nimiavaruuden etuliitteitä nimiavaruudessa määriteltyihin pääsytunnisteisiin.

nimiavaruuden std (and why to avoid it)

käyttäminen toinen tapa päästä käsiksi tunnisteisiin nimiavaruuden sisällä on käyttää direktiivilauseketta. Tässä on alkuperäinen ”Hello world” – ohjelma, jossa on käyttöohje:

1
2
3
4
5
6
7
8
9

#include <iostream>
käyttäen nimiavaruutta std; / / tämä on käytössä oleva direktiivi, joka käskee kääntäjää tarkistamaan std-nimiavaruuden ratkaistessaan tunnisteita, joilla ei ole etuliitettä
int main()
{
kout << ” Hello world!”; // koutissa ei ole etuliitettä, joten kääntäjä tarkistaa onko kout määritelty paikallisesti vai nimiavaruudessa std
return 0;
}

käyttävä direktiivi käskee kääntäjää tarkistamaan tietyn nimiavaruuden, kun hän yrittää ratkaista tunnisteen, jossa ei ole nimiavaruuden etuliitettä. Joten yllä olevassa esimerkissä, kun Kääntäjä menee määrittää, mitä tunniste kout on, se tarkistaa sekä paikallisesti (jos se on määrittelemätön) ja std nimiavaruudessa (jossa se vastaa std::kout).

monet tekstit, tutorialit ja jopa jotkut kääntäjät suosittelevat tai käyttävät ohjelman yläosassa käyttöohjetta. Tällä tavalla käytettynä tämä on kuitenkin huono käytäntö ja erittäin lannistunut.

harkitse seuraavaa ohjelmaa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#include <iostream> / / imports the declaration of std:: cout
using namespace std; / / makes STD::cout accessible as” cout ”
int cout () / / declares our our our our ”cout” function
{
paluu 5;
}
int main()
{
kout << ” Hello, world!”; / / Käännä virhe! Kenet haluamme tänne? Yksi std nimiavaruudessa vai yksi määrittelimme edellä?
paluu 0;
}

yllä oleva ohjelma ei käännä, koska kääntäjä ei voi nyt sanoa, haluammeko määrittelemämme kout-funktion vai nimiavaruuden STD sisällä määritetyn kout-funktion.

kun käytät tällä tavalla direktiiviä, määrittelemämme tunniste voi olla ristiriidassa minkä tahansa std-nimiavaruudessa olevan identtisesti nimetyn tunnisteen kanssa. Vielä pahempaa on se, että vaikka tunnistenimi ei ole nykyään ristiriidassa, se voi olla ristiriidassa uusien tunnisteiden kanssa, jotka on lisätty std-nimiavaruuteen tulevissa kieliversioissa. Tämä oli koko tarkoitus siirtää kaikki tunnisteet standard kirjasto osaksi std nimiavaruus ensinnäkin!

Varoitus

Vältä ohjeiden (kuten nimiavaruuden std;) käyttöä ohjelmasi yläosassa. Ne rikkovat sitä syytä, miksi nimiavaruuksia ylipäätään lisättiin.

puhumme enemmän lausumien käytöstä (ja siitä, miten niitä käytetään vastuullisesti) oppitunnilla 6.12 — lausumien käyttäminen.

2.9 — Johdatus esiprosessoriin

Hakemisto

2.7 — ohjelmat, joissa on useita kooditiedostoja

Vastaa

Sähköpostiosoitettasi ei julkaista.