c++ - funcion - Referencia no definida a const int estático
funcion pi en c++ (7)
Creo que este artefacto de C ++ significa que en cualquier momento que se Bar::kConst
referencia a Bar::kConst
, en su lugar se usa su valor literal.
Esto significa que en la práctica no hay una variable para hacer un punto de referencia.
Puede que tengas que hacer esto:
void func()
{
int k = kConst;
foo(k);
}
Me encontré con un problema interesante hoy. Considera este simple ejemplo:
template <typename T>
void foo(const T & a) { /* code */ }
// This would also fail
// void foo(const int & a) { /* code */ }
class Bar
{
public:
static const int kConst = 1;
void func()
{
foo(kConst); // This is the important line
}
};
int main()
{
Bar b;
b.func();
}
Al compilar recibo un error:
Undefined reference to ''Bar::kConst''
Ahora, estoy bastante seguro de que esto se debe a que la static const int
no está definida en ningún lado, lo cual es intencional porque, según mi entender, el compilador debería poder hacer el reemplazo en tiempo de compilación y no necesitar una definición. Sin embargo, dado que la función toma un parámetro const int &
parámetro, parece que no está haciendo la sustitución, sino que prefiere una referencia. Puedo resolver este problema haciendo el siguiente cambio:
foo(static_cast<int>(kConst));
Creo que esto ahora está forzando al compilador a realizar una int temporal, y luego pasa una referencia a eso, lo cual puede hacer con éxito en tiempo de compilación.
Me preguntaba si esto fue intencional, o estoy esperando demasiado de gcc para poder manejar este caso? ¿O es algo que no debería hacer por alguna razón?
Es intencional, 9.4.2 / 4 dice:
Si un miembro de datos estático es de tipo const integral o const enumeration, su declaración en la definición de clase puede especificar un constante-inicializador que será una expresión de constante integral (5.19) En ese caso, el miembro puede aparecer en expresiones constantes integrales. El miembro aún se definirá en un ámbito de espacio de nombres si se utiliza en el programa
Cuando pasa el miembro de datos estáticos por referencia constante, lo "usa", 3.2 / 2:
Una expresión se evalúa potencialmente a menos que aparezca cuando se requiere una expresión constante integral (ver 5.19), es el operando del operador sizeof (5.3.3), o es el operando del operador typeid y la expresión no designa un valor l de tipo de clase polimórfica (5.2.8). Se usa un objeto o función no sobrecargada si su nombre aparece en una expresión potencialmente evaluada.
Entonces, de hecho, "lo usas" cuando lo pasas por valor también, o en un static_cast
. Es solo que GCC te ha dejado descolgado en un caso pero no en el otro.
[Editar: gcc está aplicando las reglas de los borradores C ++ 0x: "Una función variable o no sobrecargada cuyo nombre aparece como una expresión potencialmente evaluada se usa odr-a menos que sea un objeto que satisfaga los requisitos para aparecer en una constante expresión (5.19) y se aplica inmediatamente la conversión lvalue-to-rvalue (4.1) ". La conversión estática realiza la conversión lvalue-rvalue inmediatamente, por lo que en C ++ 0x no se "usa".]
El problema práctico con la referencia constante es que foo
está dentro de sus derechos para tomar la dirección de su argumento y compararla, por ejemplo, con la dirección del argumento de otra llamada, almacenada en un sistema global. Como un miembro de datos estáticos es un objeto único, esto significa que si llama a foo(kConst)
desde dos foo(kConst)
diferentes, la dirección del objeto pasado debe ser la misma en cada caso. AFAIK GCC no puede organizar eso a menos que el objeto esté definido en una (y solo una) TU.
OK, entonces en este caso foo
es una plantilla, por lo tanto, la definición es visible en todas las TU, por lo que tal vez el compilador en teoría podría descartar el riesgo de que haga algo con la dirección. Pero, en general, ciertamente no deberías tomar direcciones o referencias a objetos inexistentes ;-)
Este es un caso realmente válido. Especialmente porque foo podría ser una función del STL como std :: count que toma una const T y como su tercer argumento.
Pasé mucho tiempo tratando de entender por qué el enlazador tenía problemas con un código tan básico.
El mensaje de error
Referencia no definida a ''Bar :: kConst''
nos dice que el enlazador no puede encontrar un símbolo.
$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 U Bar::kConst
Podemos ver desde la ''U'' que Bar :: kConst no está definido. Por lo tanto, cuando el vinculador intenta hacer su trabajo, tiene que encontrar el símbolo. Pero solo declaras kConst y no lo defines.
La solución en C ++ también es definirlo de la siguiente manera:
template <typename T>
void foo(const T & a) { /* code */ }
class Bar
{
public:
static const int kConst = 1;
void func()
{
foo(kConst); // This is the important line
}
};
const int Bar::kConst; // Definition <--FIX
int main()
{
Bar b;
b.func();
}
Luego, puede ver que el compilador colocará la definición en el archivo de objeto generado:
$nm -C main.o
0000000000000000 T main
0000000000000000 W void foo<int>(int const&)
0000000000000000 W Bar::func()
0000000000000000 R Bar::kConst
Ahora, puede ver la ''R'' diciendo que está definida en la sección de datos.
Si está escribiendo variable estática const con inicializador dentro de la declaración de clase, es como si hubiera escrito
class Bar
{
enum { kConst = 1 };
}
y GCC lo tratará de la misma manera, lo que significa que no tiene una dirección.
El código correcto debería ser
class Bar
{
static const int kConst;
}
const int Bar::kConst = 1;
También puede reemplazarlo por una función de miembro constestable:
class Bar
{
static constexpr int kConst() { return 1; };
};
Truco simple: use +
antes de que kConst
pase la función. Esto evitará que se tome una referencia de la constante, y de esta manera el código no generará una solicitud del enlazador para el objeto constante, sino que continuará con el valor constante del tiempo del compilador.
g ++ versión 4.3.4 acepta este código (ver este enlace ). Pero la versión 4.4.0 de g ++ lo rechaza.