First note that in C++, unlike in Java, users can define value types. This means that there are 2^32 * 2^32 * 2^8 possible values for a routing_entry. If you want, you can think of routing_entry as a 72-bit primitive type, although you have to be a bit careful with the analogy.
So, in Java route
can be null, and there are 2^32 * 2^32 * 2^8 + 1 usefully different values for a routing_entry
variable. In C++, it cannot be null. In Java "empty" might mean returning a null reference. In C++ only pointers can be null, and routing_entry
is not a pointer type. So in your code in this case "empty" means "I have no idea what value this thing has, because I never initialized it or assigned to it".
In Java, a routing_entry
object would be allocated on the heap. In C++ you don't want to do that unless you have to, because memory management in C++ takes effort.
You have a few (good) options:
1) add a field to the routing entry, to indicate that it has been initialised. Chances are this won't make the struct any bigger, because of padding and alignment requirements of your implementation:
struct routing_entry {
unsigned long destSeq; // 32 bits on Win32. Could be different.
unsigned long nextHop // 32 bits on Win32. Could be different.
unsigned char hopCount; // 8 bits on all modern CPUs. Could be different.
unsigned char initialized; // ditto
};
Why not use bool? Because the standard helpfully allows sizeof(bool) != 1
. It's entirely possible that a bool is implemented as an int, especially if you have an oldish C++ compiler. That would make your struct bigger.
Then make sure the struct is inited with 0 values in your function, instead of whatever garbage was on the stack:
routing_entry Cnode_router_aodv::consultTable(unsigned int destinationID ) {
routing_entry route = {};
if ( routing_table.find(destinationID) != routing_table.end() )
route = routing_table[destinationID];
return route; // will be "empty" if not found
}
And make sure that all the entriess in the map have the initialized field set to non-zero. Caller then checks initialized.
2) Use "magic" values of the existing fields as markers.
Suppose for the sake of argument that you never deal in routes with hopCount 0. Then as long as you 0-initialize as above, callers can check hopCount != 0. The max values of the types are also good flag values - since you're restricting your routes to 256 hops, chances are you won't do any harm by restricting them to 255 hops. Rather than callers having to remember this, add a method to the struct:
struct routing_entry {
unsigned long destSeq; // 32 bits
unsigned long nextHop // 32 bits
unsigned char hopCount; // 8 bits
bool routeFound() { return hopCount != (unsigned char)-1; }
};
Then you'd initialise like this:
routing_entry route = {0, 0, -1};
or if you're worried what happens when you change the order or number of fields in future:
routing_entry route = {0};
route.hopCount = -1;
And the caller does:
routing_entry myroute = consultTable(destID);
if (myroute.routeFound()) {
// get on with it
} else {
// destination unreachable. Look somewhere else.
}
3) Caller passes in a routing_entry
by pointer or non-const reference. Callee fills the answer into that, and returns a value indicating whether it succeeded or not. This is commonly called an "out param", because it sort of simulates the function returning a routing_entry
and a bool.
bool consultTable(unsigned int destinationID, routing_entry &route) {
if ( routing_table.find(destinationID) != routing_table.end() ) {
route = routing_table[destinationID];
return true;
}
return false;
}
Caller does:
routing_entry route;
if (consultTable(destID, route)) {
// route found
} else {
// destination unreachable
}
By the way, when using a map your code as it is looks up the ID twice. You can avoid this
as follows, although it's unlikely to make a noticeable difference to the performance of your app:
map< unsigned long int, routing_entry >::iterator it =
routing_table.find(destinationID);
if (it != routing_table.end()) route = *it;