Espacios de nombres
Variantes
Acciones

Reglas de ámbito

De cppreference.com
< cpp‎ | language

Cada nombre que aparece en un programa de C++ es válido solamente en uno o varios trozos discontinuos del código fuente, que se conoce como su ámbito.

Dentro de un ámbito, se usa la búsqueda de nombre no calificada para asociar el nombre con su declaración.

Contenido

[editar] Ámbito de bloque

El posible ámbito de una variable creada mediante una declaración en un bloque (instrucción compuesta) comienza en el punto de la declaración y termina al final del bloque. El ámbito actual es el mismo que el posible ámbito a no ser que en un bloque interno se declare una variable con el mismo nombre (en este caso, el ámbito de la declaración interna es excluida del ámbito de la declaración externa).

int main()
{
    int a = 0; // comienza el ámbito de la primera 'a'
    ++a; // el nombre 'a' está en el ámbito de la primera 'a' y se refiere a ella.
    {
        int a = 1; // comienza ámbito se segunda 'a'
                   // se interrumpe el ámbito de la primera 'a'
        a = 42;    // 'a' está en el ámbtio de la segunda 'a' y se refiere a ella.                 
    } // fin de bloque, fin del ámbito de la segunda 'a'
      //             se retoma el ámbito de la primera 'a'
} // fin de bloque, fin de ámbito de primera 'a'
int b = a; // ERROR: nombre 'a' no se encuentra en este ámbito

El posible ámbito de un nombre declarado en un bloque de captura de excepción comienza en el punto de declaración y termina al final de bloque de la excepción, y no se extiende a otra captura de excepción o bloque adjunto.

try {   
    f();
} catch(const std::runtime_error& re) { // comienza ámibto de 're'
    int n = 1; // comienza ámbito de 'n'
    std::cout << re.what(); // 're' está en el ámbito
} // finaliza ámbito de 're' y 'n'
 catch(std::exception& e) {
    std::cout << re.what(); // ERROR: 're' no está en el ámbito
    ++n; // ERROR: 'n' no está en al ámbito
}

El posible ámbito de un nombre declarado en la instrucción inicial de un bucle for, en la condición de un bucle for, en la declaración de rango de un bucle for basado en rango, en la instrucción inicial de la instrucción if (desde C++17) o la instrucción switch (desde C++17), en la condición de la instrucción if, un bucle while, o una instrucción switch comienza en el punto de declaración y termina en el final de la instrucción anidada.

Base* bp = new Derivada;
if(Derivada* dp = dynamic_cast<Derivada*>(bp))
{
    dp->f(); // 'dp' está en el ámbito
} // fin del ámbito de 'dp'
 
for(int n = 0; // comienza ámbito de 'n'
    n < 10;    // 'n' está en el ámbito
    ++n)       // 'n' está en el ámbito
{
    std::cout << n << ' '; // 'n' está en el ámbito
} // fin del ámbito de 'n'

[editar] Ámbito de parámetro de función

El posible ámbito de un parámetro de función (incluyendo parámetros de una expresión lambda) o de una variable predefinida local a la función comienza en su punto de declaración.

  • Si la declaración de la función que lo contiene no es la declaración de una definición de función, su posible ámbito finaliza al final de esa declaración de función.
  • Por otra parte, el posible ámbito termina al final del bloque de la última captura de excepción en un bloque try de función, o al final del cuerpo de la función si no se utiliza un bloque try de función.
const int n = 3;
 
int f1(int n,     // se interrumpe el ámbito de la 'n' global,
                  // comienza el ámbito del parámetro 'n'
       int y = n); // ERROR: el argumento por defecto referencia a un parámetro
 
int (*(*f2)(int n))[n]; // de acuerdo: el ámbito del parámetro 'n' de la función
                        // termina al final de la declaración de la función
                        // en la declaración de la matriz, el ámbito es el de la 'n' global
// (se declara un puntero a una función de devuelve un puntero a una matriz de 3 enteros)
 
// por el contrario
auto (*f3)(int n)->int (*)[n]; // ERROR: el parámetro 'n' como tamaño de la matriz
 
 
int f(int n = 2)  // comienza ámbito de 'n'
try // bloque try de función
{         // comienza el cuerpo de la función
   ++n;   // 'n' está en el ámbito y se refiere al parámetro
   {
      int n = 2; // comienza el ámbito de la variable 'n' local al bloque
                 // se interrumpe el ámbito de la 'n' parámetro de función 
      ++n; // 'n' se refiere a la variable local del bloque
    }            // el ámbito de la variable local 'n' termina
                 // se retoma el ámbito de la 'n' parámetro de función
} catch(...) {
   ++n; // 'n' está en el ámbito y se refiere al parámetro de la función
   throw;
} // finaliza el la última captura de excepciones, el ámbito del parámetro 'n' termina
int a = n; // ERROR: el nombre 'n' no está en el ámbito

[editar] Ámbito de función

Una etiqueta (y solamente una etiqueta) declarada dentro de una función tiene su ámbito en toda la función, en todos los bloques internos, antes y después de su propia declaración.

void f()
{
   {   
       goto label; // label está en el ámbito aunque se declare posteriormente
label:;
   }
   goto label; // label ignora el ámbito de bloques
}
 
void g()
{
    goto label; // ERROR: label no está en el ámbito de g()
}

[editar] Ámbito de espacio de nombres

El posible ámbito de una entidad declarada en un espacio de nombres comienza en la declaración y consiste en la concatenación de todas las definiciones de espacios de nombres del mismo espacio de nombres que le siguen, más, cualquierdirectiva using que inserta este nombre o todo el espacio de nombres en otro ámbito, para el resto de ese ámbito.

El ámbito de mayor nivel en una unidad de traducción (ámbito de archivo o ámbito global) es también un espacio de nombres y se denomina ámbito de espacio de nombres global. El posible ámbito de cualquier entidad declarada en el ámbito del espacio de nombres global comienza en la declaración y continua hasta el final de la unidad de traducción.

El ámbito de una entidad declarada en un espacio de nombres sin nombre (anónimo) o en un espacio de nombres inline incluye el espacio de nombres del nivel superior.

namespace N { // comienza el ámbito de N (como miembro del espacio de nombres global)
    int i; // comienza ámbito de 'i'
    int g(int a) { return a; } // comienza ámbito de 'g'
    int j(); // comienza ámbito de 'j'
    void q(); // comienza ámbito de 'q'
    namespace {
        int x; // comienza ámbito de 'x'
    } // no termina el ámbito de 'x' (espacio de nombres anónimo).
    inline namespace inl { // comienza ámbito de inl
      int y; // comienza ámbito de 'y'
    } // no termina el ámbito de 'y' (espacio de nombres inline)
} // se interrumpe ámbito de 'i', 'g', 'j', 'q', inl, 'x', 'y'
 
namespace {
    int l=1; // comienza ámbito de 'l'
} // no termina ámbito de 'l' (es miembro de un espacio de nombres anónimo)
 
namespace N { // continua ámbito de 'i', 'g', 'j', 'q', inl, 'x', 'y'
    int g(char a) {  // sobrecarga N::g(int)
        return l+a;  // l esta en el ámbito desde el espacio de nombres anónimo
    }
    int i; // ERROR: definición duplicada ('i' todavía está en el ámbito)
    int j(); // de acuerdo: se permite repetir la declaración de una función
    int j() { // de acuerdo: definición de la función declarada anteriormente N::j()
        return g(i); // llama a N::g(int)
    }
    int q(); // ERROR: 'q' está en el ámbito con un tipo de retorno distinto
} // se interrumpe ámbito de 'i', 'g', 'j', 'q', inl, 'x', 'y'
 
int main() {
    using namespace N; // se retoma ámbito de 'i', 'g', 'j', 'q', inl, 'x', 'y'
    i = 1; // N::i está en ámbito
    x = 1; // N::(anónimo)::x está en ámbito
    y = 1; // N::inl::y está en ámbito
    inl::y = 2; // N::inl tambie´n está en ámbito
} // se interrumpe ámbito de  'i', 'g', 'j', 'q', inl, 'x', 'y'

[editar] Ámbito en una clase

El posible ámbito de un nombre declarado en una clase comienza en el punto de declaración y se extiende por el resto del cuerpo de la clase y los cuerpos de todas las funciones (incluso si se definen fuera de la definición de la case o antes de la declaración del nombre), parámetros por defecto, especificación de excepciones, incializadores de llaves o igual en la clase, condiciones de depuración(desde C++20), y todos estos elementos en las clases anidadas, recursivamente.

class X {
    int f(int a = n) { // X::n está en el ámbito dentro del parámetro por defecto
         return a*n;   // X::n está en el ámbito dentro del cuerpo de una función
    }
    int g();
    int i = n*2;   // X::n está en el ámbito dentro de un inicializador
 
//  int x[n];      // ERROR: n no está en el ámbito del cuerpo de la clase
    static const int n = 1;
    int x[n];      // de acuerdo: n ahora está en el ámbito del cuerpo de la clase
};
int X::g() { return n; } // X::n está en el ámbito en el cuerpo de una función miembro fuera de la clase

Si un nombre se usa en el cuerpo de una clase antes de su declaración, y otra declaración de este nombre está en ámbito, el programa es erróneo.

typedef int c; // ::c
enum { i = 1 }; // ::i
class X {
    char v[i]; // ERROR: en este punto, i se refiere a ::i
               // pero también está X::i
    int f() {
         return sizeof(c); // de acuerdo: X::c,  ::c no está en el ámbito dentro de la función miembro
    }
    char c; // X::c
    enum { i = 2 }; // X::i
};
 
typedef char* T;
struct Y {
    T a; // ERROR: en este punto, T se refiere a ::T
         // pero también está Y::T
    typedef long T;
    T b;
};

Los nombres de los miembros de la clase solo pueden usarse en cuatro contextos:

  • en su propio ámbito de clase o en el ámbito de las clase derivadas
  • después del operador . aplicado en una expresión del tipo de la clase o sus derivadas
  • después del operador -> aplicado en una expresión del tipo puntero a la clase o sus derivadas.
  • después del operador :: aplicado a un nombre de la clase o sus derivadas.

[editar] Ámbito de enumeración

El nombre de un enumerador dentro de una enumeración con ámbito comienza en el punto de declaración y finaliza al final del especificador enum (por el contrario, los enumeradores sin ámbito están en ámbito después del final del especificador enum).

enum e1_t { // enumeración sin ámbito
  A,
  B = A*2
}; // no termina ámbito de A y B
 
enum class e2_t { // enumeración con ámbito
    SA,
    SB = SA*2 // SA está en ámbito
}; // finaliza ámbito de SA y SB
 
e1_t e1 = B; // de acuerdo, B está en ámbito
// e2_t e2 = SB; // ERROR: SB no está en ámbito
e2_t e2 = e2_t::SB; // de acuerdo

[editar] Ámbito de parámetro de plantilla

El posible ámbito del nombre de un parámetro de plantilla comienza en el punto de declaración y continua hasta el final de la declaración de la plantilla más interna en la que fue introducido. En concreto, se puede usar un parámetro de plantilla en la declaración de los subsiguientes parámetros de plantilla y en la especificación de la clase base, pero no en la declaración de los parámetros de plantilla que le preceden.

template< typename T, // comienza ámbito de T
          T* p,       // se puede usar T para un parámetros sin tipo
          class U = T // se puede usar T para un tipo por defecto
        >
class X : public Array<T> // se puede usar T en el nombre de clase base
{
   // se puede usar T dentro del cuerpo
}; // finaliza ámbito de T y U, el ámbito de X continua.

El posible ámbito del nombre de un parámetro de plantilla en un parámetro de plantilla es la lista de parámetros mas interna en la que aparece el nombre.

template< template< // plantilla de parámetro de plantilla 
                    typename Y,     // comienza ámbito de Y
                    typename G = Y // Y está en el ámbito
                  > // finaliza el ámbito de Y y G
          class T,
//          typename U = Y // ERROR: Y no está en el ámbito
          typename U
        >
class X
{
}; // termina el ámbito de T y U

Del mismo modo que otros ámbitos anidados, el nombre de un parámetro de plantilla anula el mismo nombre de un ámbito más externo durante la duración de este:

typedef int N;
template< N X, // parámetro sin tipo de tipo int
          typename N, // comienza ámbito de esta N, se interrumpe el ámbito de ::N
          template<N Y> class T // N es aquí el parámetro de plantilla, no int
         > struct A;

[editar] Punto de declaración

El ámbito comienza en el punto de declaración, que se encuentra en:

Para variables y otros nombres creados por declaración simple, el punto de declaración es inmediatamente después del declarador del nombre y antes de su inicializador, si tiene:

unsigned char x = 32; // comienza el ámbito de la primera 'x'
{
    unsigned char x = x; // el ámbito de la segunda 'x' comienza antes del inicializador (= x)
                         // no se inicializa la segunda 'x' con el valor 32, 
                         // se inicializa la segunda 'x' consigo mismo, valor indeterminado
}
std::function<int(int)> f = [&](int n){return n>1 ? n*f(n-1) : n;};
           // el nombre de la función 'f' está en el ámbito dentro de la lambda, y puede
           // ser capturado correctamente por referencia, dando una función recursiva
const int x = 2; // comienza ámbito de la primara 'x'
{
    int x[x] = {}; // el ámbito de la sgunda 'x' comienza antes que la inicialización (= {})
                   // pero después del declarador (x[x]). Dentro del declarador, la 'x' externa
                   // todavía está en ámbito. Se declara una matriz de 2 enteros.
}

El punto de declaración de una clase o plantilla es inmediatamente después de que el identificador que nombra la clase (o el identificador de plantilla que nombra a la plantilla) aparezca en su cabecera de clase, y ya está en ámbito en el listado de las clases base:

// el nombre 'S' está en el ámbito inmediatamente después de aparecer, 
// por lo que puede usarse en el listado de clase base
struct S: std::enable_shared_from_this<S> 
{
};

El punto de la declaración de una enumeración es inmediatamente después de que el identificador que lo nombra aparezca en el {{rlp | enum | especificador enum |} o en declaración de enum opaco, lo que primero se use:

enum E : int { // E está ya en ámbito
    A = sizeof(E)
};

El punto de declaración de un alias de tipo o plantilla es inmediatamente después del id de tipo al que refiere el alias:

using T = int; // el punto de declaración de T se encuentra en el punto y coma
using T = T;   // igual que T = int

El punto de declaración de una enumeración es inmediatamente después de su definición (no antes del inicializador como en las variables):

const int x = 12;
{
    enum { x = x + 1, // punto de declaración en la coma, 'x' toma el valor 13
           y = x + 1  // el enumerado 'x' ahora está en ámbito, 'y' toma el valor 14
         };
}

El punto de declaración para un nombre de clase inyectada sigue inmediatamente a la llave de apertura de su definición de clase (o plantilla de clase)

template<typename T>
struct Array
// : std::enable_shared_from_this<Array> // ERROR: la clase inyectada no está en ámbito
   : std::enable_shared_from_this< Array<T> > //de acuerdo: la plantilla Array está en ámbito
{ // el nombre de clase inyectado Array está ahora en el ámbito como un miembro público
    Array* p; // puntero a Array<T>
};

[editar] Referencias

  • El estándar C++11 (ISO/IEC 14882:2011):
  • 3.3 Scope [basic.scope]
  • C++98 standard (ISO/IEC 14882:1998):
  • 3.3 Declarative regions and scopes [basic.scope]

[editar] Véase también

Documentación de C de Reglas de ámbito