Reputation: 371
Currently I am writing a class that supports data proccessing on the cpu
or gpu
utilizing preprocessor definitions to determine which header
file to include.
IE
#ifdef CPU_work
#include "cpu_backend.h"
#endif
#ifdef GPU_work
#include "gpu_backend.h"
#endif
class Work {
//Implementation dependant upon included header
}
However, there maybe instances where I would need both variants. Is there anyway I could do something like....
namespace CPU {
#define CPU_work
//Generate implementation of WorkClass with cpu_backend.h
}
namespace GPU {
#define GPU_work
//Generate implementation of WorkClass with gpu_backend.h
}
and therefor determine which implementation I want via something like...
CPU::Work cpuObject;
GPU::Work gpuObject;
Would be happy with any work-arounds also. Much thanks JJ.
Upvotes: 1
Views: 1388
Reputation: 2875
Interesting question:) If I understood your goals correct, I can suggest a few solutions.
First uses template specialization, template default arguments and (of course) some macros.
Check this out:
// cpu_backend.h
#define CPU_BACKEND
class UseCPU;
#ifndef GPU_BACKEND
template<class Method = UseCPU>
struct Backend;
#endif
template<>
struct Backend<UseCPU>
{
char* Info() { return "CPU"; }
};
// gpu_backend.h
#define GPU_BACKEND
class UseGPU;
#ifndef CPU_BACKEND
template<class Method = UseGPU>
struct Backend;
#endif
template<>
struct Backend<UseGPU>
{
char* Info() { return "GPU"; }
};
// main.cpp
// Try to swap comments on headers
// and see how output changes
#include "cpu_backend.h"
//#include "gpu_backend.h"
#include <iostream>
template<class ... Method>
struct Work
{
Work()
{
std::cout << "I use " << backend.Info() << std::endl;
}
private:
Backend<Method ...> backend;
};
int main()
{
Work<> work;
// Uncomment these two while including both headers
//Work<UseCPU> cpuWork;
//Work<UseGPU> gpuWork;
return 0;
}
If you use MSVC you can simplify example above eliminating #define
and #ifndef
.
Trick: MSVC (2017 and maybe earlier versions) allow to omit that macros thresh, just ignoring the second declaration if they meet in the same compilation unit, like this:
template<class Method = UseCPU> struct Backend; template<class Method = UseGPU> struct Backend;
BUT this will be not standard. Standard does not allow specifying default template args twice.
Meanwhile, this solution has few drawback:
When you include both headers, someone still can say Work<>
which will
use the backend specified by the first header you included.
However, it would be better if compiler forced a person to specify a
backend type explicitly in this circumstances, because otherwise it
relies on the header inclusion order which is bad (say hello to
macros).
Also, it assumes that both backends have the same API (like Info()
in my case)
Possible Fixes for those:
I am sure it is possible to make compiler give an error when both headers are included and no explicit backend was specified, but it probably involves more preprocessor things or some SFINAE...
If your backends do have different APIs, then you can insert a few
#ifdef
where needed or (preferably) use C++17
if constexpr(std::is_same<Method, UseCPU>()::value)
if you have access
to such cool features:)
Upvotes: 0
Reputation: 900
This might be the place to use a template method design. Your base class implements everything that is common to both CPU and GPU and then you use abstract functions where there are differences.
class Work {
public:
void execute() {
// Do some initializing
foo();
// Do some middle stuff
bar();
// Do some final stuff
}
private:
virtual void foo() = 0;
virtual void bar() = 0;
}
class CpuWork: public Work {
virtual void foo() {
// Do some CPU stuff
}
virtual void bar() {
// Do some more CPU stuff
}
}
class GpuWork: public Work {
virtual void foo() {
// Do some GPU stuff
}
virtual void bar() {
// Do some more GPU stuff
}
}
You now can't use your base class Work
by accident since it's abstract and you can't accidentally invoke your derived classes foo
or bar
since they are private members of the base class.
Upvotes: 3