编译同一类的两个不同实现

Posted

技术标签:

【中文标题】编译同一类的两个不同实现【英文标题】:Compiling two different implementations of the same class 【发布时间】:2017-08-09 01:35:40 【问题描述】:

目前我正在编写一个类,它支持在 cpugpu 上进行数据处理,利用预处理器定义来确定要包含的 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,因为它是抽象的,并且您不能意外调用派生类foobar,因为它们是基类的私有成员。

【讨论】:

【参考方案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&lt;&gt; 这将 使用您包含的 第一个 标头指定的后端。 但是,如果编译器强制一个人指定一个 在这种情况下明确的后端类型,否则它 依赖于错误的标题包含顺序(问好 宏)。

此外,它假定两个后端具有相同的 API(如 Info() 就我而言)

可能的修复:

我确信当两者都出现时编译器会出错 包含标头并且未指定显式后端,但它 可能涉及更多的预处理器或一些 SFINAE...

如果您的后端确实有不同的 API,那么您可以插入一些 #ifdef 在需要的地方或(最好)使用 C++17 if constexpr(std::is_same&lt;Method, UseCPU&gt;()::value) 如果您有访问权限 这么酷的功能:)

【讨论】:

绝对是一个很酷的技巧,但我更愿意保持它的标准。似乎这只是要求错误。 对不起,可能我写错了。我提供的代码是标准的(我想是 C++11)。 Visual Studio 中的“技巧”不是标准的,但它只是让您在没有#ifndef GPU_BACKEND 和朋友的情况下做同样的事情。如果这就是你所说的“标准”:)

以上是关于编译同一类的两个不同实现的主要内容,如果未能解决你的问题,请参考以下文章

java面向对象(多态)

java中多态的要点

第59课 类模板深度剖析

如何使 cmakelists.txt 编译使用在其他地方声明和实现的函数和类的 cpp

多态与抽象以及接口

Makefile:来自相同源的两个目标使用不同的标志编译两次