2008-11-07 18 views
172

¿Alguien puede explicar por qué el siguiente código no se compilará? Al menos en g ++ 4.2.4.Referencia no definida al miembro de la clase estática

Y, lo que es más interesante, ¿por qué compilará cuando lanzo MEMBER a int?

#include <vector> 

class Foo { 
public: 
    static const int MEMBER = 1; 
}; 

int main(){ 
    vector<int> v; 
    v.push_back(Foo::MEMBER);  // undefined reference to `Foo::MEMBER' 
    v.push_back((int) Foo::MEMBER); // OK 
    return 0; 
} 
+0

He editado la pregunta para sangrar el código por cuatro espacios en lugar de usar

. Esto significa que los corchetes angulares no se interpretan como HTML. –

+0

aplausos :) regla de 10 caracteres min es a veces molesto;) –

Respuesta

176

Necesita definir el miembro estático en algún lugar (después de la definición de la clase). Pruebe esto:

class Foo { /* ... */ }; 

const int Foo::MEMBER; 

int main() { /* ... */ } 

Eso debería deshacerse de la referencia indefinida.

+3

Buen punto, la inicialización const const entero en línea crea una constante entera de ámbito que no puede tomar la dirección de, y el vector toma un param de referencia. –

+9

Esta respuesta solo aborda la primera parte de la pregunta. La segunda parte es mucho más interesante: ¿por qué agregar un molde NOP hace que funcione sin requerir la declaración externa? – nobar

+27

Acabo de pasar un buen rato averiguando que si la definición de clase está en un archivo de cabecera, entonces la asignación de la variable estática debe estar en el archivo de implementación, no en el encabezado. – shanet

69

El problema se debe a un interesante choque de nuevas características de C++ y a lo que intenta hacer. En primer lugar, vamos a echar un vistazo a la firma push_back:

void push_back(const T&) 

Está esperando una referencia a un objeto de tipo T. Bajo el antiguo sistema de inicialización, tal miembro existe. Por ejemplo, el código siguiente se compila bien:

#include <vector> 

class Foo { 
public: 
    static const int MEMBER; 
}; 

const int Foo::MEMBER = 1; 

int main(){ 
    std::vector<int> v; 
    v.push_back(Foo::MEMBER);  // undefined reference to `Foo::MEMBER' 
    v.push_back((int) Foo::MEMBER); // OK 
    return 0; 
} 

Esto es porque no es un objeto real en alguna parte que tiene ese valor almacenado en ella. Sin embargo, si cambia al nuevo método de especificación de miembros estáticos de const, como ya ha dicho, Foo::MEMBER ya no es un objeto. Es una constante, algo parecido a:

#define MEMBER 1 

Pero sin los dolores de cabeza de una macro preprocesador (y con seguridad de tipos). Eso significa que el vector, que está esperando una referencia, no puede obtener uno.

+1

Esto tiene sentido completo –

+1

gracias, eso ayudó ... que podría calificar para http://stackoverflow.com/questions/1995113/strangest-language-feature si no está allí ya ... –

+1

También vale la pena señalar que MSVC acepta la versión sin cast sin quejas. – porges

1

No hay idea de por qué funciona el molde, pero Foo :: MEMBER no está asignado hasta la primera vez que se carga Foo, y como nunca lo está cargando, nunca se asigna. Si tuviera alguna referencia a un Foo en alguna parte, probablemente funcionaría.

+0

Creo que está respondiendo su propia pregunta: el elenco funciona porque crea una referencia (temporal). –

56

El estándar de C++ requiere una definición para su miembro static const si la definición es de alguna manera necesaria.

La definición es obligatoria, por ejemplo, si se usa su dirección. push_back toma su parámetro por referencia const, y tan estrictamente el compilador necesita la dirección de su miembro y necesita definirlo en el espacio de nombres.

Cuando publica explícitamente la constante, está creando una temporal y es esta temporal la que está vinculada a la referencia (bajo reglas especiales en el estándar).

Este es un caso realmente interesante, y de hecho creo que vale la pena plantear un problema para que el estándar se cambie para tener el mismo comportamiento para su miembro constante.

Aunque, de una manera extraña, esto podría ser visto como un uso legítimo del operador unario '+'. Básicamente el resultado de la unary + es un valor de lado derecho y por lo que las reglas para la unión de rvalues ​​a const aplican referencias y no usamos la dirección de nuestro método constante estática:

v.push_back(+Foo::MEMBER); 
+2

+1. Sí, es ciertamente extraño que para un objeto x del tipo T, la expresión "(T) x" se pueda usar para unir una const ref mientras que la simple "x" no pueda. ¡Me encanta tu observación sobre "unario +"! ¿Quién hubiera pensado que ese pobre "unario +" en realidad tenía un uso ... :) –

+3

Pensando en el caso general ... ¿Hay algún otro tipo de objeto en C++ que tenga la propiedad de que (1) se pueda usar? como valor l solo si se ha definido pero (2) se puede convertir a un valor r sin definir? –

+0

Buena pregunta, y al menos por el momento no puedo pensar en ningún otro ejemplo. Probablemente esto solo esté aquí porque el comité simplemente estaba reutilizando la sintaxis existente. –

9

Aaa.h

class Aaa { 

protected: 

    static Aaa *defaultAaa; 

}; 

Aaa.CPP

// You must define an actual variable in your program for the static members of the classes 

static Aaa *Aaa::defaultAaa; 
+0

+1 'Una respuesta corta' – dariush

0

En cuanto a la segunda pregunta: push_ref toma como parámetro de referencia, y no se puede tener una referencia a memeber const estático de una clase/estructura. Una vez que llame a static_cast, se crea una variable temporal. Y se puede pasar una referencia a este objeto, todo funciona bien.

O al menos mi colega que resolvió esto dijo que sí.

1

con C++ 11, lo anterior sería posible que los tipos básicos como

class Foo { 
public: 
    static constexpr int MEMBER = 1; 
}; 

La parte constexpr crea una expresión estática en contraposición a una variable de estática - y que se comporta como una definición de método en línea extremadamente simple. Sin embargo, el enfoque resultó un poco tambaleante con constexprs C-string dentro de las clases de plantillas.

Cuestiones relacionadas