Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance: typedef vs wrapper class for primitive types?

I want to define a new type in C++ which is just some primitive type (in my example an int, could be any type). I call the type NodeId in this example.

I could just use typedef int NodeId. I want a default value for NodeIds, so I would use #define NULL_NODE_ID -1.

Now, I think it would be nicer to define a class instead of typedef to allow a function isValid() and a default constructor constructing a null 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;}
    //...
};

Are there any performance disadvantages which result in using the second method?

like image 567
Misch Avatar asked Jul 03 '12 22:07

Misch


1 Answers

Actually, there are two reasons this could conceivably be slower.

First, there's no way to create an uninitialized NodeId. Normally, that's a good thing. But imagine you have code like this:

NodeId nodeid;
foo.initializeNodeId(&nodeid);

You'll be doing an extra assignment which isn't actually necessary.

You can fix that by adding a special constructor. It's probably much better to create a Foo::createNodeId() so you don't need Foo::initializeNodeId(&NodeId), but if you don't control the definition of Foo, that may not be possible.

Second, a NodeId is not a compile-time constant expression. As dasblinkenlight suggests, this is far more likely to cause problems with code not being legal than to cause performance problems, but both are possible. (Why? Because you may be forcing the compiler to insert code to do some calculation at runtime that could have been done at compile time, if you were using an int. Not that this is likely to be an issue for a class called NodeId…)

Fortunately, if you're using C++11, you can fix that with constexpr. And if you want your code to also be legal C++03, you can deal with that with a macro.

Also, as pointed out by dasblinkenlight, you're missing const in two methods.

Finally, there's no reason to write "inline" on methods that are defined inside the class definition; they're already inherently inline.

Putting it all together:

#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;}
    //...
};

Now you can do this, to avoid the cost of an extra -1 assignment.

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

And this, to legally use a NodeId as a non-type template parameter:

myClassTemplate<NodeId(3)> c;

Or, this, to be sure the compiler can legally just initialize x to 4:

int x = 3;
x += NodeId(1);
like image 95
abarnert Avatar answered Oct 12 '22 11:10

abarnert