2010-01-06 14 views
9

Supongamos que tengo una clase como¿Llamar al constructor de una clase vacía realmente usa alguna memoria?

class Empty{ 
    Empty(int a){ cout << a; } 
} 

Y luego invocarlo usando

int main(){ 
    Empty(2); 
    return 0; 
} 

Will esta causa cualquier memoria que se asignará en la pila para la creación de un objeto "vacío"? Obviamente, los argumentos deben ser empujados a la pila, pero no quiero incurrir en gastos adicionales. Básicamente, estoy usando el constructor como un miembro estático.

La razón por la que quiero hacer esto es por las plantillas. El código real se parece

template <int which> 
class FuncName{ 
    template <class T> 
    FuncName(const T &value){ 
     if(which == 1){ 
      // specific behavior 
     }else if(which == 2){ 
      // other specific behavior 
     } 
    } 
}; 

que me permite escribir algo como

int main(){ 
    int a = 1; 
    FuncName<1>(a); 
} 

por lo que llego a especializar un parámetro de plantilla, al no tener que especificar el tipo de T. Además, espero que el compilador optimice las otras ramas dentro del constructor. Si alguien sabe si esto es cierto o cómo verificarlo, sería muy apreciado. También asumí que lanzar plantillas a la situación no cambia el problema de la "clase vacía" de arriba, ¿es así?

+0

La pregunta es por qué te importa.El trabajo del compilador es cuidar y generar el mejor código. Deberías concentrarte en escribir el código más expresivo. –

+0

PS. No hay ningún requisito para que el argumento sea empujado en la pila. El C++ ABI no está definido de manera deliberada, por lo que los compiladores tienen la oportunidad de usar el registro para pasar los parámetros, si eso hace que el código sea más eficiente. –

+0

Me importa porque quiero el mejor rendimiento posible; Realmente odio la actitud de que el código sea elegante y no te preocupes por estas cosas. A veces, estas cosas importan (yo hago informática de alto rendimiento). –

Respuesta

16

Citando BS:

¿Por qué es el tamaño de una clase vacía no es cero? Para garantizar que las direcciones de dos objetos diferentes serán diferentes. Por la misma razón, "nuevo" siempre devuelve punteros a objetos distintos. Consideremos:

class Empty { }; 

void f() 
{ 
    Empty a, b; 
    if (&a == &b) cout << "impossible: report error to compiler supplier"; 

    Empty* p1 = new Empty; 
    Empty* p2 = new Empty; 
    if (p1 == p2) cout << "impossible: report error to compiler supplier"; 
} 

hay una regla interesante que dice que una clase base vacía no tiene por qué ser representado por un byte separado:

struct X : Empty { 
    int a; 
    // ... 
}; 

void f(X* p) 
{ 
    void* p1 = p; 
    void* p2 = &p->a; 
    if (p1 == p2) cout << "nice: good optimizer"; 
} 

Esta optimización es seguro y puede ser más útil. Permite a un programador utilizar clases vacías para representar conceptos muy simples sin gastos generales. Algunos compiladores actuales proporcionan esta "optimización de clase base vacía".

+0

Pero, ¿y si el objeto creado nunca se referencia en absoluto? ¿Se le permite al compilador reservar espacio sin pila o inmediatamente limpiarlo aunque no salga del alcance? –

+0

Sí, estoy seguro de que un compilador eliminará por completo la memoria reservada si no se utiliza o no se hace referencia a ella. Es una optimización bastante simple. Dependiendo del compilador, por supuesto. –

+1

Mientras el comportamiento no se modifique, el compilador puede eliminar el concepto completo del objeto debajo del capó y todo el asunto puede insertarse en el código y todos los valores almacenados en los registros. –

2

Podría, podría ser, no, dependiendo de las circunstancias. Si dices:

Empty e; 
Empty * ep = & e; 

entonces, obviamente, las cosas tienen que ser asignadas.

+0

¿El compilador necesariamente tiene que emitir código _allocate_ algo para darle una dirección? ¿Hay alguna razón por la cual & e no puede ser una dirección que no sea ni montón ni pila, y donde no hay un objeto real? – James

+0

@Autopulated: Creo que sí, vea el comentario de Richard Pennington. La pregunta más interesante es si nunca pides su dirección; entonces, ¿todavía tiene que tener uno? (Una pregunta muy similar al zen) –

+1

Si toma la dirección de e, y luego hace algo con esa dirección (que mi código de ejemplo no), e debe ser instanciado, es decir, asignado. –

2

Pruébalo y mira. Muchos compiladores eliminarán dichos objetos temporales cuando se les pida que optimicen su salida.

Si el desmontaje es demasiado complejo, a continuación, crear dos funciones con diferente número de tales objetos y ver si hay alguna diferencia en los lugares de la pila de objetos que les rodea, algo así como:

void empty1 (int x) 
{ 
    using namespace std; 

    int a; 
    Empty e1 (x); 
    int b; 

    cout << endl; 
    cout << "empty1" << endl; 
    cout << hex << int (&x) << " " << dec << (&x - &a) << endl; 
    cout << hex << int (&a) << " " << dec << (&a - &b) << endl; 
} 

y luego tratar ejecutando eso en comparación con una función empty8 con ocho vacíos creados. Con g ++ en x86, si toma la dirección de cualquiera de los vacíos obtiene una ubicación entre x y a en la pila, por lo tanto, incluye x en la salida. No puede suponer que el almacenamiento de los objetos terminará en el mismo orden en que se declararon en el código fuente.

+0

¿Cómo? Estoy mirando la lista de ensamblaje de g ++ para ver un ejemplo simple y es muy difícil de seguir. Estoy usando 'g ++ -c -g -O2 -Wa, -ahl = file.s file.cpp' –

+0

Puede obtener una mejor compilación en un archivo ejecutable (con información de depuración), luego use' objdump -dS', y busque las líneas de origen que le interesen. Como tiene activada la optimización, recuerde que la misma línea de origen puede aparecer en varios lugares del desmontaje. –

Cuestiones relacionadas