This might be an easy question, I don't master C++11 templates at all.
I have a generic vector class that is not std::vector<T>
for performance reasons (very specific code).
I have observed that checking whether T
is a POD or not and, when it is, perform special computations, is much more efficient than not :
void vec<T>::clear() {
if (!std::is_pod<T>::value) {
for (int i = 0; i < size; i++) {
data[i].~T();
}
}
size = 0;
}
Here, I don't call the destructor of T
for each item (size
can be really huge) and performance is really boosted. But the test if (!std::is_pod<T>::value)
is useless once the template was compiled : rather than being compiled to :
void vec<int>::clear() {
if (false) {
for (int i = 0; i < size; i++) {
data[i].~int();
}
}
size = 0;
}
I want it to be compiled to :
void vec<int>::clear() {
size = 0;
}
Is the compiler "clever" enough to skip if (false)
blocks or if (true)
tests ? Do I have to write that code somewhat differently ?
Is the compiler "clever" enough to skip if (false) blocks or if (true) tests?
Yes, definitely. Dead code elimination is a trivial optimisation that is performed routinely. Its existence is also crucial to make many debugging libraries work efficiently (= without runtime overhead in release mode).
But I would probably still rewrite this to make it visible to the reader that this is a compile-time distinction, by overloading the function based on is_pod
:
void vec<T>::do_clear(std::true_type) { }
void vec<T>::do_clear(std::false_type) {
for (int i = 0; i < size; i++) {
data[i].~T();
}
}
void vec<T>::clear() {
do_clear(std::is_trivially_destructible<T>());
size = 0;
}
In the above code I’m using is_trivially_destructible
instead of is_pod
to make the code more self-explanatory, as suggested by Nicol in the comments. This technique is commonly employed in standard library implementations and other libraries. It’s known as tag dispatching.
There is a language feature called pseudo destructors which is specifically designed for what you want to do. Basically given a type template parameter T you can syntactically call a destructor for it, and if, when instantiated, T is a scalar type (because for example it is a fundamental type like an int
) it will compile and generate a no-op in its place.
For the remainder of POD types that are not scalar, they have trivial destructors, so will likewise generate a no-op.
Any production compiler on even the lowest optimization setting will elide a loop over a no-op. So you can safely write:
void vec<T>::clear() {
for (int i = 0; i < size; i++) {
data[i].~T();
}
size = 0;
}
Basically, you are trying to solve an imaginary performance problem the compiler is already taking care of for you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With