Salta al contenuto
Note per Studenti Esercizio - I tipi di scope in C++ (secondo appello 2021-22, teoria)

Esercizio - I tipi di scope in C++ (secondo appello 2021-22, teoria)

Esame
In questa pagina 3

Domanda aperta 2 del secondo appello 2021-22 (11 febbraio 2022) del corso di Laboratorio di Programmazione, Ing. Informatica UniPD. Teoria: C++ - scope, namespace e librerielo scope è la regione di testo in cui un nome è valido, dal punto di dichiarazione alla fine dello scope; i tipi di scope sono globale (di file), di classe, locale (blocco o lista argomenti di una funzione), di statement (for) e di namespace; funzioni dentro funzioni sono illegali, le classi locali da evitare; i namespace raggruppano nomi senza creare un tipo e si accedono col nome qualificato namespace::membro oppure con using (la using directive va evitata negli header); un header file è un insieme di dichiarazioni che chi lo include può usare (<> per i file di sistema, "" per quelli del progetto), dev'essere protetto dalle include guard (#ifndef/#define/#endif) per non includere due volte le stesse definizioni; il linker unisce i file oggetto con le librerie, statiche (copiate in ogni eseguibile, self-contained) o dinamiche (una sola copia condivisa, .so su Linux, .dll su Windows, ricompilabili senza toccare gli eseguibili).C++ - scope, namespace e librerie →. Una domanda simile, con la differenza fra scope e durata, è nel terzo appello 2022-23: Esercizio - Scope e durata della memorizzazione (terzo appello 2022-23, teoria).

Testo. Definire il concetto di scope in C++. Elencare e descrivere tutti i tipi di scope presenti in C++. Riportare il codice C++ con un esempio di scope per ognuno dei tipi elencati.

Definizione

Scope. Regione del testo di un programma. Ogni nome è dichiarato in uno scope ed è valido (in scope) dal punto della dichiarazione fino alla fine di quello scope. L'obiettivo è rendere i nomi locali ("Locality is good"), per evitare interferenze (clash): per questo i nomi sono tanto più descrittivi quanto più è grande il loro scope.

I tipi di scope

tipo regione caratteristiche
globale (di file) fuori da ogni altro scope funzioni, tipi e costanti; le variabili globali sono visibili e modificabili ovunque nel file: da usare con attenzione (nessun incapsulamento, debug difficile)
di classe il testo dentro una classe i membri sono visibili in tutta la classe (anche quelli dichiarati dopo); fuori si accede con oggetto.membro o Classe::membro
locale dentro un blocco { } o nella lista degli argomenti di una funzione dal punto di dichiarazione alla }; i blocchi si annidano
di statement dentro l'istruzione, per esempio nell'intestazione di un for la variabile del for non esiste dopo il ciclo
di namespace uno scope con nome, dentro lo scope globale o un altro namespace raggruppa classi, funzioni, dati e tipi senza creare un tipo; si raggiunge con namespace::nome

Scope annidati: uno scope dentro un altro. Sono leciti blocchi in blocchi, funzioni dentro classi (il caso più frequente e utile), classi dentro classi (raro), classi in funzioni (da evitare); non sono legali le funzioni dentro funzioni. Un nome interno nasconde uno esterno uguale (shadowing), raggiungibile con :: se è globale.

Un esempio per ogni tipo

scope_tipi.cppcpp
#include <iostream>

int global_counter = 100;            // scope GLOBALE (di file): visibile ovunque sotto

namespace Geometria {                // scope di NAMESPACE: uno scope con nome
    constexpr double pi = 3.14159;
    double area_cerchio(double r) { return pi * r * r; }
}

class Contatore {                    // scope di CLASSE: il testo dentro la classe
public:
    void incrementa() { ++valore; }  // valore e' visibile qui, anche se dichiarato dopo
    int leggi() const { return valore; }
private:
    int valore = 0;
};

int somma(int a, int b)              // a e b: scope LOCALE (lista degli argomenti)
{
    int risultato = a + b;           // scope locale: dal punto di dichiarazione alla }
    return risultato;
}

int main()
{
    int x = 5;                       // locale a main
    {                                // un blocco crea uno scope annidato
        int x_interno = x * 2;       // esiste solo qui dentro
        std::cout << "blocco: x_interno = " << x_interno << '\n';
    }
    // std::cout << x_interno;       // errore: x_interno non e' piu' in scope

    for (int i = 0; i < 3; ++i)      // scope di STATEMENT: i vive solo nel for
        global_counter += i;
    // std::cout << i;               // errore: i non esiste fuori dal for

    Contatore c;
    c.incrementa();
    c.incrementa();

    std::cout << "somma = " << somma(2, 3) << '\n';
    std::cout << "global_counter = " << global_counter << '\n';
    std::cout << "contatore = " << c.leggi() << '\n';
    std::cout << "area = " << Geometria::area_cerchio(1.0) << '\n';  // nome qualificato
    return 0;
}
blocco: x_interno = 10
somma = 5
global_counter = 103
contatore = 2
area = 3.14159
  • global_counter: scope globale; il for la modifica di 0+1+2=30 + 1 + 2 = 3: 100→103100 \to 103.
  • namespace Geometria: scope di namespace; la funzione si chiama con Geometria::area_cerchio(1.0) =π⋅12=3.14159= \pi \cdot 1^2 = 3.14159.
  • class Contatore: scope di classe; valore è privato ma visibile dentro incrementa() e leggi().
  • somma(int a, int b): a, b, risultato hanno scope locale (parametri e corpo della funzione).
  • il blocco { int x_interno = ... } in main è uno scope locale annidato: x_interno esiste solo lì; // std::cout << x_interno; non compilerebbe.
  • for (int i = 0; ...): scope di statement; i non è più visibile dopo il ciclo.

Un esempio di shadowing: nel programma seguente la x interna nasconde le altre due e ::x raggiunge la globale.

scope_ombra.cppcpp
#include <iostream>

int x = 1;                    // globale

int main()
{
    std::cout << "x globale = " << x << '\n';
    int x = 2;                // locale: NASCONDE la globale (shadowing)
    std::cout << "x locale  = " << x << '\n';
    {
        int x = 3;            // ancora piu' interno: nasconde quella di main
        std::cout << "x interna = " << x << '\n';
        std::cout << "x globale con :: = " << ::x << '\n';  // operatore di scope senza nome = globale
    }
    std::cout << "di nuovo x locale = " << x << '\n';
    return 0;
}
x globale = 1
x locale  = 2
x interna = 3
x globale con :: = 1
di nuovo x locale = 2

Versione ripasso

tipo dove esempio
globale fuori da tutto int global_counter = 100;
classe dentro la classe valore privato di Contatore, visibile in incrementa()
locale blocco {} o argomenti di funzione a, b, risultato in somma; { int x_interno; }
statement nell'istruzione for (int i = 0; ...): i muore col ciclo
namespace scope con nome namespace Geometria { ... } →\to Geometria::area_cerchio(1.0)
  • Annidamenti: funzioni in classi (utile), blocchi (inevitabili), classi in classi (raro), classi locali (da evitare), funzioni in funzioni: illegali. Shadowing: il nome interno nasconde l'esterno; ::x = globale.
  • Programma: global_counter 100 →\to 103 con il for (0+1+20+1+2); Geometria::area_cerchio(1.0) = 3.14159; somma(2, 3) = 5; Contatore incrementato due volte →\to 2; shadowing: x globale 1, locale 2, interna 3, ::x = 1.
  • Errori tipici: usare x_interno o i fuori dal loro scope; confondere scope e durata (Esercizio - Scope e durata della memorizzazione (terzo appello 2022-23, teoria)); dimenticare che le variabili globali sono accessibili da ogni funzione; definire una funzione dentro un'altra.

Esercizi su questo argomento

Teoria collegata