7.6 — Powiązanie wewnętrzne

W lekcji 7.3 -- Zmienne lokalne powiedzieliśmy: „Powiązanie identyfikatora określa, czy inne deklaracje tej nazwy odnoszą się do tego samego obiektu, czy nie” i omówiliśmy, w jaki sposób zmienne lokalne mają no linkage.

Zmienne globalne, a identyfikatory funkcji mogą mieć internal linkage lub external linkage. W tej lekcji omówimy przypadek powiązania wewnętrznego, a przypadek powiązania zewnętrznego na lekcji 7.7 -- Powiązania zewnętrzne i deklaracje zmiennych forward.

Identyfikator z powiązanie wewnętrzne można zobaczyć i wykorzystać w ramach jednej jednostki tłumaczeniowej, ale nie jest on dostępny z innych jednostek tłumaczeniowych. Oznacza to, że jeśli dwa pliki źródłowe mają identycznie nazwane identyfikatory z wewnętrznym powiązaniem, identyfikatory te będą traktowane jako niezależne (i nie będą skutkować naruszeniem ODR z powodu duplikacji definicji).

Kluczowa informacja

Identyfikatory z wewnętrznym powiązaniem mogą w ogóle nie być widoczne dla linkera. Alternatywnie mogą być widoczne dla linkera, ale oznaczone do użycia tylko w określonej jednostce tłumaczeniowej.

Powiązana treść

Jednostki tłumaczeniowe omówimy na lekcji 2.10 — Wprowadzenie do preprocesora.

Zmienne globalne z wewnętrznym powiązaniem

Zmienne globalne z wewnętrznym powiązaniem są czasami nazywane zmiennymi wewnętrznymi.

Aby niestała zmienna globalna była wewnętrzna, używamy static słowo kluczowe.

#include <iostream>

static int g_x{}; // non-constant globals have external linkage by default, but can be given internal linkage via the static keyword

const int g_y{ 1 }; // const globals have internal linkage by default
constexpr int g_z{ 2 }; // constexpr globals have internal linkage by default

int main()
{
    std::cout << g_x << ' ' << g_y << ' ' << g_z << '\n';
    return 0;
}

Zmienne globalne Const i constexpr mają domyślnie wewnętrzne powiązanie (i dlatego nie potrzebują static słowa kluczowego — jeśli zostanie użyte, zostanie zignorowane).

Oto przykład wielu plików wykorzystujących zmienne wewnętrzne:

a.cpp:

[[maybe_unused]] constexpr int g_x { 2 }; // this internal g_x is only accessible within a.cpp

main.cpp:

#include <iostream>

static int g_x { 3 }; // this separate internal g_x is only accessible within main.cpp

int main()
{
    std::cout << g_x << '\n'; // uses main.cpp's g_x, prints 3

    return 0;
}

Ten program wypisuje:

3

Ponieważ g_x jest wewnętrzne dla każdego pliku, main.cpp nie ma pojęcia, że a.cpp ma również zmienna o nazwie g_x (i odwrotnie).

Dla zaawansowanych czytelników

Użycie static słowa kluczowego powyżej jest przykładem specyfikatora klasy pamięci, który ustawia zarówno powiązanie nazwy, jak i czas jej przechowywania. Najczęściej używany storage class specifiersstatic, extern, I mutable. Termin storage class specifier jest najczęściej używany w dokumentacji technicznej.

Dla zaawansowanych czytelników

Standard C++11 (dodatek C) wyjaśnia, dlaczego zmienne const mają domyślnie wewnętrzne powiązania: „Ponieważ obiekty const mogą być używane jako wartości w czasie kompilacji w C++, ta funkcja nakłania programistów do zapewnienia jawnych wartości inicjalizujących dla każdej stałej. Ta funkcja pozwala użytkownikowi na umieszczanie obiektów const w plikach nagłówkowych, które są zawarte w wielu kompilacjach jednostek.”

Projektantom C++ zamierzono dwie rzeczy:

  • Obiekty Const powinny nadawać się do stosowania w wyrażeniach stałych. Aby można było ich używać w wyrażeniach stałych, kompilator musi widzieć definicję (a nie deklarację), aby została oceniona w czasie kompilacji.
  • Obiekty const powinny mieć możliwość propagowania poprzez pliki nagłówkowe.

Obiekty z zewnętrznym połączeniem można definiować tylko w pojedynczej jednostce translacyjnej bez naruszania ODR - inne jednostki translacyjne muszą uzyskać dostęp do tych obiektów poprzez deklarację forward. Jeżeli obiekty const miałyby domyślnie połączenie zewnętrzne, można by ich używać tylko w wyrażeniach stałych w pojedynczej jednostce tłumaczenia zawierającej definicję i nie można by ich skutecznie propagować za pomocą plików nagłówkowych, ponieważ #włączenie nagłówka do więcej niż jednego pliku źródłowego skutkowałoby naruszeniem ODR.

Obiekty z wewnętrznym powiązaniem mogą mieć definicję w każdej jednostce tłumaczenia, gdzie są potrzebne, bez naruszania ODR. Pozwala to na umieszczenie obiektów const w pliku nagłówkowym i #włączenie do dowolnej liczby jednostek tłumaczeniowych bez naruszania ODR. A ponieważ każda jednostka translacyjna ma raczej definicję niż deklarację, gwarantuje to, że te stałe mogą być używane w wyrażeniach stałych w obrębie jednostki translacyjnej.

Zmienne wbudowane (które mogą mieć zewnętrzne powiązania bez powodowania naruszeń ODR) zostały wprowadzone dopiero w C++ 17.

Funkcje z wewnętrznym powiązaniem

Jak zauważono powyżej, identyfikatory funkcji również mają powiązania. Funkcje domyślnie korzystają z powiązań zewnętrznych (co omówimy w następnej lekcji), ale można je ustawić na powiązania wewnętrzne za pomocą static słowem kluczowym:

add.cpp:

// This function is declared as static, and can now be used only within this file
// Attempts to access it from another file via a function forward declaration will fail
[[maybe_unused]] static int add(int x, int y)
{
    return x + y;
}

main.cpp:

#include <iostream>

int add(int x, int y); // forward declaration for function add

int main()
{
    std::cout << add(3, 4) << '\n';

    return 0;
}

Ten program nie będzie łączył, ponieważ funkcja add nie jest dostępna poza add.cpp.

Regułą jednej definicji i powiązaniem wewnętrznym

W lekcji 2.7 -- Deklaracje przesyłania dalej i definicje. Zauważyliśmy, że reguła jednej definicji mówi, że obiekt lub funkcja nie może mieć więcej niż jednej definicji, ani w pliku, ani w pliku program.

Warto jednak zaznaczyć, że obiekty wewnętrzne (i funkcje) zdefiniowane w różnych plikach uznawane są za niezależne byty (nawet jeśli ich nazwy i typy są identyczne), zatem nie ma tu mowy o naruszeniu zasady jednej definicji. Każdy obiekt wewnętrzny ma tylko jedną definicję.

static w porównaniu z nienazwanymi przestrzeniami nazw

We współczesnym C++ użycie słowa kluczowego static do podawania wewnętrznych powiązań identyfikatorów wypada z łask. Nienazwane przestrzenie nazw mogą dawać wewnętrzne powiązania z szerszym zakresem identyfikatorów (np. identyfikatorami typów) i lepiej nadają się do nadawania wewnętrznych powiązań wielu identyfikatorom.

Nienazwane przestrzenie nazw omówimy w lekcji >7.14 -- Nienazwane i wbudowane przestrzenie nazw.

Po co zawracać sobie głowę dawaniem wewnętrznych powiązań identyfikatorów?

Zazwyczaj są dwa powody, aby je podać identyfikatory wewnętrzne powiązanie:

  • Istnieje identyfikator, który chcemy mieć pewność, że nie będzie dostępny dla innych plików. Może to być zmienna globalna, z którą nie chcemy się bawić, lub funkcja pomocnicza, której nie chcemy wywoływać.
  • Być pedantycznym w unikaniu kolizji nazewnictwa. Ponieważ identyfikatory z wewnętrznym powiązaniem nie są widoczne dla linkera, mogą kolidować jedynie z nazwami w tej samej jednostce tłumaczenia, a nie w całym programie.

Wiele nowoczesnych przewodników programistycznych zaleca podawanie każdej zmiennej i funkcji, która nie jest przeznaczona do użycia, z innego wewnętrznego powiązania pliku. Jeśli masz dyscyplinę, jest to dobra rekomendacja.

Na razie zalecamy co najmniej łagodniejsze podejście: podaj wewnętrzne powiązanie z dowolnym identyfikatorem, dla którego masz wyraźny powód, aby uniemożliwić dostęp z innych plików.

Najlepsza praktyka

Nadaj wewnętrzne powiązanie identyfikatorom, gdy masz wyraźny powód, aby zabronić dostępu z innych plików.

Rozważ nadanie wszystkich identyfikatorów, których nie chcesz, aby były dostępne dla innych plików. (użyj w tym celu nienazwanej przestrzeni nazw).

Szybkie podsumowanie

// Internal global variables definitions:
static int g_x;          // defines non-initialized internal global variable (zero initialized by default)
static int g_x{ 1 };     // defines initialized internal global variable

const int g_y { 2 };     // defines initialized internal global const variable
constexpr int g_y { 3 }; // defines initialized internal global constexpr variable

// Internal function definitions:
static int foo() {};     // defines internal function

Obszerne podsumowanie przedstawiamy w lekcji 7.12 — Podsumowanie zakresu, czasu trwania i powiązania.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
149 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze