2012-07-03 14 views
7

Quiero definir un nuevo tipo en C++ que es solo un tipo primitivo (en mi ejemplo, un int, podría ser de cualquier tipo). Llamo al tipo NodeId en este ejemplo.Rendimiento: typedef vs clase contenedor para los tipos primitivos?

tan sólo pudiera utilizar typedef int NodeId. Quiero un valor predeterminado para NodeId s, entonces usaría #define NULL_NODE_ID -1.

Ahora, creo que sería mejor para definir una clase en lugar de typedef para permitir una función isValid() y un constructor por defecto la construcción de un nulo NodeId:

class NodeId 
{ 
    int value; 
public: 
    inline NodeId() : value(-1) {} 
    inline NodeId(int value) : value(value) {} 
    inline operator int() {return value;} 
    inline bool isValid() {return value != -1;} 
    //... 
}; 

¿Hay algunas desventajas de rendimiento que resultan en el uso de la segundo método?

+0

Si usa un nuevo compilador, entonces debería ser lo mismo que usar el int. – mfontanini

+0

@mfontanini Entonces, ¿por qué rechazaste la pregunta? Esta es la respuesta a la pregunta, debe publicarlo como tal. – leemes

+0

@leemes ¿por qué estás asumiendo que fui yo? – mfontanini

Respuesta

6

En realidad, hay dos razones que esto posiblemente podría ser más lento.

En primer lugar, no hay forma de crear un NodeId no inicializado. Normalmente, eso es bueno cosa. Pero imagine que tiene un código como éste:

NodeId nodeid; 
foo.initializeNodeId(&nodeid); 

Se va a realizar una misión extra que no es realmente necesario.

Puede solucionarlo agregando un constructor especial. Probablemente sea mucho mejor crear un Foo :: createNodeId() por lo que no necesita Foo :: initializeNodeId (& NodeId), pero si no controla la definición de Foo, puede que no sea posible.

En segundo lugar, un NodeId no es una expresión constante en tiempo de compilación. Como dasblinkenlight sugiere, es mucho más probable que cause problemas con el código que no sea legal que causar problemas de rendimiento, pero ambos son posibles. (¿Por qué? Porque puede estar forzando al compilador a insertar código para hacer algunos cálculos en tiempo de ejecución que podrían haberse hecho en tiempo de compilación, si estaba usando un int. No es probable que esto sea un problema para una clase llamada NodeId ...)

Afortunadamente, si estás usando C++ 11, se puede arreglar eso con constexpr. Y si quiere que su código también sea legal C++ 03, puede lidiar con eso con una macro.

Además, como ha señalado dasblinkenlight, que se está perdiendo const en dos métodos.

Por último, no hay razón para escribir "en línea" en los métodos que se definen dentro de la definición de la clase; ya están intrínsecamente en línea.

Poniendo todo junto:

#if __cplusplus > 201000L 
#define CONSTEXPR_ constexpr 
#else 
#define CONSTEXPR_ 
#endif 

class NodeId 
{ 
    int value; 
public: 
    struct Uninitialized {}; 
    CONSTEXPR_ NodeId() : value(-1) {} 
    CONSTEXPR_ NodeId(Uninitialized) {}  
    CONSTEXPR_ NodeId(int value) : value(value) {} 
    CONSTEXPR_ operator int() const {return value;} 
    CONSTEXPR_ bool isValid() const {return value != -1;} 
    //... 
}; 

Ahora usted puede hacer esto, para evitar el costo de un -1 asignación adicional.

NodeId nodeId(NodeId::Uninitialized()); 
foo.initializeNodeId(&nodeid); 

Y esto, para usar legalmente un NODEID como un parámetro de plantilla no Tipo:

myClassTemplate<NodeId(3)> c; 

O, esto, para estar seguro de que el compilador puede legalmente solo inicializar X a 4:

int x = 3; 
x += NodeId(1); 
+0

Gracias por la gran respuesta :) – Misch

+0

abarnert, ¿existe la posibilidad de admitir la aritmética de números enteros fuera de la caja? 'Nodo NodeId; node ++; 'no compila. Tengo que implementar 'operator ++' manualmente. ¿Es esto posible sin implementar manualmente esos operadores triviales? – leemes

+0

@leemes: No, no completamente de forma automática. Pero boost.operator puede ayudarlo a definir algunos explícitamente y obtener otros gratis. Si está haciendo muchas de estas clases, puede usar CRTP, una clase base o un generador de código, de modo que al menos solo tiene que definir todos los operadores una vez. O defina una plantilla de clase única "SmartInt", y luego haga algo como "struct NodeIdTag {}; typedef SmartInt NodeId;" (que por supuesto puedes resumir en una macro). – abarnert

4

Si utiliza un compilador relativamente nuevo, el código generado debería ser el mismo. El compilador no debería tener problemas para delimitar esas funciones miembro. En caso de que realmente tenga dudas al respecto, siempre puede desmontar el ejecutable.

+0

Eso no es del todo cierto. 'int' es un tipo POD, pero' NodeId' no lo es. El compilador generará un código de inicialización adicional cuando se use, se le prohíbe usarlo en uniones, y "infectará" a otras clases con su no PODness. –

+0

'NodeId x;' debe producir el mismo código que "int x (-1);", ya que esa es la forma en que OP usaría ese tipo si fuera un 'int'. Sin embargo, el argumento que no es POD es cierto. – mfontanini

+0

@DanHulme C++ 11 [permite] (https://en.wikipedia.org/wiki/C%2B%2B11#Unrestricted_unions) no agregados en uniones – Praetorian

3

Si los ajustes de optimización del compilador permiten al compilador en línea todo, no debe haber diferencias de rendimiento. La única desventaja que puedo detectar es que las expresiones basadas en objetos de esta clase pueden ya no calificar como constantes de tiempo de compilación, que pueden ser importantes en la programación de plantillas. Aparte de eso, terminas con claridad adicional sin costo de tiempo de ejecución.


P.S. Se echa en falta en su const operator int y isValid:

inline operator int() const {return value;} 
inline bool isValid() const {return value != -1;} 
+1

"expresiones constantes en tiempo de compilación *", ¿quizás? –

+1

tienes razón sobre la const, acabo de piratear este ejemplo para publicarlo aquí. – Misch

1

Si está planeando convertir esto en una biblioteca que pueda ser utilizada por algún otro lenguaje de programación (como si estuviera escribiendo esto en una DLL de C++), existen claras ventajas de tenerlo como typedef int.

Por ejemplo, cuando escribe un contenedor de python o un contenedor de Java para su API, no tiene tantos dolores de cabeza como para que su clase y tipos vuelvan a su puerto. Entonces, todo lo que tiene que preocuparse es el tamaño de bit del int.

Cuestiones relacionadas