So, how is it type unsafe ?
void SortElements(void* MyArray, // what type is pointed here?
unsigned int N, // Are there really N elements?
size_t size, // Is the size correct?
int(*cmp)(void*,void*)); // Is this the correct function?
The code that you present is type-unsafe, not because of the function pointer but rather because of the use of void*
in both the SortElements
signature and the signature of the function pointer.
The reason why this is unsafe is because the caller has the whole responsibility of passing the right arguments, and the compiler cannot ensure that the pointer MyArray
points to a contiguous memory region that holds iNumofElems
each of which has the size
offered in the interface. If the programmer makes a mistake, the compiler will not be able to help there, if a maintainer modifies the type stored in the array (size changes) or the number of elements, the compiler will not be able to detect it and tell you that you need to update the call to SortElements
. Finally, because the function pointer that is passed also uses void*
, the signature of a comparator that compares apples and pears is exactly the same, and the compiler cannot help if you pass the incorrect function pointer.
struct Apple {
int weight;
};
struct Pear {
double weight;
};
int compare_pears( void * pear1, void * pear2 ) {
return static_cast<Pear*>(pear1)->weight - static_cast<Pear*>(pear2)->weight;
}
int main() {
Apple apples[10];
SortElements( apples, 20, sizeof(Pear), compare_pears );
}
While the compiler is able to verify that the signature of the function pointer matches the signature that the function needs, the function pointer itself is unsafe, and allows you to pass a comparator for basically anything.
Compare that with this other alternative:
template <typename T, std::size_t N>
void SortElements( T (&array)[N], int (*cmp)( T const &, T const & ) );
Here the compiler will infer the type of the elements T
and the size of the array N
from the call. There is no need to pass the size of T
, as the compiler knows it. The comparator function passed to this version of SortElements
is strongly typed: it takes two constant references to the type of the element stored in the array and returns an int
. If we tried this in the previous program:
int compare_pears( Pear const & lhs, Pear const & rhs );
int compare_apples( Apple const & l, Apple const & r );
Apple array[10];
//SortElements( array, compare_pears ); // Error!!!!
SortElements( array, compare_apples ); // Good!
You cannot mistake the size of the array or the size of the elements, if someone changes the type Apple
, the compiler will pick it up, if the size of the array changes, the compiler will pick it up. You cannot mistake the comparator that is passed to the function as the compiler will also pick it up. Now the program is type safe, even if it uses function pointers (that might have an impact in performance as they inhibit inlining, which is why std::sort
is usually faster than qsort
)