Hello everyone,
I am currently working on a resource manager class to handle different kinds of assets: textures, font, sounds...
In order to handle the different types of resource, I'm using a class template. Most of the methods are common no matter the resource type but I need specific logic to load them.
Based on what I have read here and there, I can think of 3 ways to do this:
* Template specialization
* CRTP
* Derived class and pure virtual function
I tried to illustrate those 3 options with the 3 following examples where f1 represent a function common to all the types and f2 a function that would need to be defined based on each type.
Template specialization:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
#include <iostream>
template<typename T>
class BaseClass {
public:
BaseClass(T val)
: myVar(val)
{}
void f1() // Common implementation no matter type T
{
std::cout << "Call to f1" << std::endl;
}
void f2(); // No general implementation => one implementation per type T
private:
T myVar;
};
template<>
void BaseClass<int>::f2()
{
std::cout << "Call to f2 for int" << std::endl;
}
template<>
void BaseClass<float>::f2()
{
std::cout << "Call to f2 for float" << std::endl;
}
int main()
{
BaseClass<int> myIntObj(5);
BaseClass<float> myFloatObj(5.f);
BaseClass<double> myDoubleObj(5); // Possible to create the object even if the function f2 is not defined for that type
myIntObj.f2();
myFloatObj.f2();
//myDoubleObj.f2(); // Build error => f2 does not exist for type double
return 0;
}
| |
CRTP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
|
#include <iostream>
template<typename Derived, typename T>
class BaseClass {
public:
BaseClass(T val)
: myVar(val)
{}
void f1() // Common implementation no matter type T
{
std::cout << "Call to f1" << std::endl;
}
void f2(const std::string& l_path) { // No general implementation => one implementation per type T
return static_cast<Derived*>(this)->f2(l_path);
}
private:
T myVar;
};
class MyIntClass : public BaseClass<MyIntClass, int>
{
public:
MyIntClass(int val)
: BaseClass(val)
{}
void f2()
{
std::cout << "Call to f2 for int" << std::endl;
}
};
class MyFloatClass : public BaseClass<MyFloatClass, float>
{
public:
MyFloatClass(float val)
: BaseClass(val)
{}
void f2()
{
std::cout << "Call to f2 for float" << std::endl;
}
};
class MyDoubleClass : public BaseClass<MyDoubleClass, double>
{
public:
MyDoubleClass(double val)
: BaseClass(val)
{}
// No def for f2
};
int main()
{
MyIntClass myIntObj(5);
MyFloatClass myFloatObj(5.f);
MyDoubleClass myDoubleObj(5); // No build error even though f2 function is not defined for that type
myIntObj.f2();
myFloatObj.f2();
//myDoubleObj.f2(); // Build error => f2 not defined for class MyDoubleClass
return 0;
}
| |
Derived class and pure virtual function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
|
#include <iostream>
template<typename T>
class BaseClass {
public:
BaseClass(T val)
: myVar(val)
{}
void f1() // Common implementation no matter type T
{
std::cout << "Call to f1" << std::endl;
}
virtual void f2() = 0; // No general implementation => one implementation per type T
private:
T myVar;
};
class MyIntClass : public BaseClass<int>
{
public:
MyIntClass(int val)
: BaseClass(val)
{}
void f2()
{
std::cout << "Call to f2 for int" << std::endl;
}
};
class MyFloatClass : public BaseClass<float>
{
public:
MyFloatClass(float val)
: BaseClass(val)
{}
void f2()
{
std::cout << "Call to f2 for float" << std::endl;
}
};
class MyDoubleClass : public BaseClass<double>
{
public:
MyDoubleClass(double val)
: BaseClass(val)
{}
//No definition of f2
};
int main()
{
MyIntClass myIntObj(5);
MyFloatClass myFloatObj(5.f);
//MyDoubleClass myDoubleObj(5); // Build error => pure virtual function not implemented in the class MyDoubleClass
myIntObj.f2();
myFloatObj.f2();
//myDoubleObj.f2(); // Build error => pure virtual function not implemented in the class MyDoubleClass
return 0;
}
| |
All those examples are giving the same output.
The question is a bit general but I read a book in which they were using the CRTP technique and can't figure out why they would not simply use template specialization. I could not find anything that I think would not work with it.
What would be the pros and cons of those 3 techniques?
From what I read one advantage of CRTP compare to virtual functions is that it avoid runtime polymorphism and thus improve performance.
Also I was a bit surprised to see that the pure virtual function example was compiling since I have read several times that template are not compatible with pure virtual function. Is it something that newer compiler can handle and that older one could not?
Thank you in advance,
Have a great day!