编译同一类的两个不同实现
Posted
技术标签:
【中文标题】编译同一类的两个不同实现【英文标题】:Compiling two different implementations of the same class 【发布时间】:2017-08-09 01:35:40 【问题描述】:目前我正在编写一个类,它支持在 cpu
或 gpu
上进行数据处理,利用预处理器定义来确定要包含的 header
文件。
IE
#ifdef CPU_work
#include "cpu_backend.h"
#endif
#ifdef GPU_work
#include "gpu_backend.h"
#endif
class Work
//Implementation dependant upon included header
但是,在某些情况下,我可能需要这两种变体。无论如何我可以做类似的事情......
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
因此确定我想要通过类似...的实现方式
CPU::Work cpuObject;
GPU::Work gpuObject;
也会对任何变通方法感到满意。 非常感谢JJ。
【问题讨论】:
**我想我会有很多人对继承发表评论。然而,这两个不同的版本除了对 gpu/cpu 的单行方法调用外,将具有相同的代码,尽管只创建两个单独的子类就足够了,我不想花时间复制/粘贴 30 多页的代码。 这实际上是一个单一接口“backend.h”的好案例,其中所有声明的函数都与一个命名空间和两个cpp文件“cpu_work.cpp”和“gpu_work.cpp”相同" 以自己的特殊方式实现通用接口。在构建时链接到正确的实现文件。 在 C++ 中执行此操作的正确方法是使用继承。打开你的 C++ 书中解释如何创建从父类继承的子类的章节,然后开始阅读。您将拥有一个基类和两个实现适当 CPU 或 GPU 特定功能的子类。 c++11 标准库不是对时钟做类似的事情吗?std::chrono::system_clock::now()
和 std::chrono::high_resolution_clock::now()
【参考方案1】:
这可能是使用模板方法设计的地方。您的基类实现了 CPU 和 GPU 通用的所有内容,然后您在存在差异的地方使用抽象函数。
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
您现在不能意外使用您的基类Work
,因为它是抽象的,并且您不能意外调用派生类foo
或bar
,因为它们是基类的私有成员。
【讨论】:
【参考方案2】:有趣的问题:) 如果我理解你的目标是正确的,我可以提出一些解决方案。
首先使用模板特化、模板默认参数和(当然)一些宏。
看看这个:
// 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;
如果您使用 MSVC,您可以简化上面的示例,消除 #define
和 #ifndef
。
技巧: MSVC(2017 年和可能更早的版本)允许忽略宏 thresh,如果它们相遇,则忽略第二个声明 同一个编译单元,像这样:
template<class Method = UseCPU> struct Backend; template<class Method = UseGPU> struct Backend;
但是这不是标准的。标准不允许指定默认模板参数两次。
同时,这个解决方案也有一些缺点:
当您同时包含两个标题时,仍然有人可以说 Work<>
这将
使用您包含的 第一个 标头指定的后端。
但是,如果编译器强制一个人指定一个
在这种情况下明确的后端类型,否则它
依赖于错误的标题包含顺序(问好
宏)。
此外,它假定两个后端具有相同的 API(如 Info()
就我而言)
可能的修复:
我确信当两者都出现时编译器会出错 包含标头并且未指定显式后端,但它 可能涉及更多的预处理器或一些 SFINAE...
如果您的后端确实有不同的 API,那么您可以插入一些
#ifdef
在需要的地方或(最好)使用 C++17
if constexpr(std::is_same<Method, UseCPU>()::value)
如果您有访问权限
这么酷的功能:)
【讨论】:
绝对是一个很酷的技巧,但我更愿意保持它的标准。似乎这只是要求错误。 对不起,可能我写错了。我提供的代码是标准的(我想是 C++11)。 Visual Studio 中的“技巧”不是标准的,但它只是让您在没有#ifndef GPU_BACKEND
和朋友的情况下做同样的事情。如果这就是你所说的“标准”:)以上是关于编译同一类的两个不同实现的主要内容,如果未能解决你的问题,请参考以下文章