Rendimiento: typedef vs clase contenedora para tipos primitivos?
Frecuentes
Visto 3,776 equipos
7
Quiero definir un nuevo tipo en C++ que es solo un tipo primitivo (en mi ejemplo, un int
, puede ser de cualquier tipo). llamo al tipo NodeId
en este ejemplo.
Podría usar typedef int NodeId
. Quiero un valor predeterminado para NodeId
s, así que usaría #define NULL_NODE_ID -1
.
Ahora, creo que sería mejor definir una clase en lugar de typedef
para permitir una función isValid()
y un constructor predeterminado que construye un valor 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 alguna desventaja de rendimiento que resulte en el uso del segundo método?
4 Respuestas
6
En realidad, hay dos razones por las que esto podría ser más lento.
Primero, no hay forma de crear un NodeId no inicializado. Normalmente, eso es un bueno cosa. Pero imagina que tienes un código como este:
NodeId nodeid;
foo.initializeNodeId(&nodeid);
Estarás haciendo una tarea adicional que en realidad no es necesaria.
Puede arreglar eso agregando un constructor especial. Probablemente sea mucho mejor crear un Foo::createNodeId() para que no necesite 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 sugiere dasblinkenlight, es mucho más probable que esto cause problemas con el código que no es legal que problemas de rendimiento, pero ambos son posibles. (¿Por qué? Porque puede estar obligando al compilador a insertar código para hacer algún cálculo en tiempo de ejecución que podría haberse hecho en tiempo de compilación, si estuviera usando un int. No es probable que esto sea un problema para una clase llamada NodeId... )
Afortunadamente, si usa C++ 11, puede solucionarlo con constexpr. Y si desea que su código también sea C++03 legal, puede solucionarlo con una macro.
Además, como lo señaló dasblinkenlight, te falta const en dos métodos.
Finalmente, no hay razón para escribir "en línea" en métodos que están definidos dentro de la definición de clase; ya están inherentemente en línea.
Poniendolo 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 puede hacer esto para evitar el costo de una asignación adicional de -1.
NodeId nodeId(NodeId::Uninitialized());
foo.initializeNodeId(&nodeid);
Y esto, para usar legalmente un NodeId como parámetro de plantilla sin tipo:
myClassTemplate<NodeId(3)> c;
O esto, para asegurarse de que el compilador pueda inicializar legalmente x a 4:
int x = 3;
x += NodeId(1);
Respondido 03 Jul 12, 23:07
Gracias por esa gran respuesta :) - misch
abarnert, ¿existe la posibilidad de admitir la aritmética de enteros lista para usar? NodeId node; node++;
no compila tengo que implementar el operator++
a mano. ¿Es esto posible sin implementar manualmente esos operadores triviales? - lemes
@leemes: No, no de forma completamente automática. Pero boost.operator puede ayudarlo a definir algunos explícitamente y obtener otros de forma gratuita. Si está haciendo muchas de estas clases, puede usar CRTP, una clase base o un generador de código, por lo que al menos solo tiene que definir todos los operadores una vez. O defina una sola plantilla de clase "SmartInt" y luego haga algo como "struct NodeIdTag{}; typedef SmartInt NodeId;" (que, por supuesto, puede incluir en una macro). - abarnert
@abarnert Encontré una manera realmente simple de admitir todos los operadores aritméticos listos para usar: simplemente defina otro operator int&()
:) - lemes
@leemes: intente definir dos clases diferentes de envoltura de int con el operador int & y pruebe todas las combinaciones de aritmética. ¿Pero combinar eso con un truco de herencia/CRTP/plantilla etiquetada podría ser suficiente? - abarnert
4
Si usa un compilador relativamente nuevo, entonces el código generado debería ser el mismo. El compilador no debería tener problemas para incorporar esas funciones miembro. En caso de que realmente tengas dudas al respecto, siempre puedes desmontar el ejecutable.
Respondido 03 Jul 12, 23:07
Eso no es del todo cierto. int
es un tipo POD, pero NodeId
no es. El compilador generará un código de inicialización adicional donde se use, está prohibido usarlo en uniones y 'infectará' a otras clases con su no POD. - dan hulme
NodeId x;
debería 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 no POD es cierto. - mfontanini
@DanHulme C++11 permite no agregados en uniones - pretoriano
3
Si la configuración de optimización del compilador permite que el compilador alinee todo, no debería haber diferencias de rendimiento. La única desventaja que puedo detectar es que las expresiones basadas en objetos de esta clase ya no califican como constantes de tiempo de compilación, lo que puede ser importante en la programación de plantillas. Aparte de eso, obtiene claridad adicional sin costo de tiempo de ejecución.
pd te falta
const
en tu operator int
y isValid
:
inline operator int() const {return value;}
inline bool isValid() const {return value != -1;}
Respondido 03 Jul 12, 23:07
1
Si está planeando convertir esto en una biblioteca que pueda ser utilizada por algún otro lenguaje de programación (como si está escribiendo esto en una DLL de C++), hay ventajas definitivas en tenerlo como typedef int
.
Por ejemplo, cuando está escribiendo un contenedor de python o un contenedor de Java para su API, no tiene tanto dolor de cabeza para que su clase y tipos se transfieran. Entonces todo lo que tiene que preocuparse es el tamaño de bit del int
.
Respondido 03 Jul 12, 23:07
No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas c++ inline micro-optimization or haz tu propia pregunta.
Si usa un compilador nuevo, debería ser lo mismo que usar el int. - mfontanini
@mfontanini Entonces, ¿por qué rechazaste la pregunta? Esta es la respuesta a la pregunta, debe publicarla como tal. - leemes
@leemes ¿por qué asumes que fui yo? - mfontanini
No relacionado:
const NodeId NullNodeId = -1;
es mejor que el#define
. - R. Martinho Fernandesno, no quiero que sea explícito en el constructor, ya que quiero que la clase se comporte tanto como sea posible como un tipo básico. - Misch