【问题标题】:Strategy for wrapping multiple libraries in C++在 C++ 中包装多个库的策略
【发布时间】:2012-12-11 14:40:42
【问题描述】:

我有一个类Foo,我不直接实现它,而是包装外部库(例如FooXternal1FooXternal2) 我见过的一种方法是使用预处理器指令作为

#include "config.h"
#include "foo.h"
#ifdef _FOOXTERNAL1_WRAPPER_
//implementation of class Foo using FooXternal1
#endif

#ifdef _FOOXTERNAL2_WRAPPER_
//implementation of class Foo using FooXternal2
#endif

config.h 用于定义这些预处理器标志(_FOOXTERNAL1_WRAPPER__FOOEXTERNAL2_WRAPPER_)。 我的印象是 C++ 程序员社区不赞成这种做法,因为它使用预处理器指令,难以调试等。此外,它不允许两种实现并行存在。

我考虑过将Foo 设为基类并从它继承以允许两个实现并行存在。但是我遇到了两个问题:

  1. 纯虚函数:cannot instatiate an object of type 'Foo',使用时需要用到。
  2. 虚拟函数存在运行没有(正确)实现的对象的风险。

我错过了什么吗?有没有更清洁的方法来做到这一点?

编辑: 总而言之,有 3(.5?!) 种包装方式 - 2(.5) 由 icepack 提供,最后一种由 Sergey 提供 1-使用工厂方法 2-使用预处理器指令 2.5- 使用 makefile 或 IDE 有效地完成预处理器指令的工作 3.5- 使用 Sergey 建议的模板

我正在开发一个资源有限的嵌入式系统,我决定使用template<enum = default_library>,具有模板专业化功能。便于后期用户理解;至少我是这么认为的

【问题讨论】:

  • 只要您链接两个外部库,提供一个虚拟类和两个具体类就是一个很好的解决方案——尤其是在运行时您可以确定应该使用哪个库时。如果您没有在两个库中链接(或者如果它们有名称空间冲突禁止它),那么这将不起作用。 #define / #ifdef 并不总是友好的,但它有时是最好的解决方案。
  • 要添加到 mah,如果您使用包装器 Foo,只需将任何必须被覆盖的函数声明为纯虚拟函数。一旦您尝试实例化具体子类,编译器将强制您在具体子类中提供实现。
  • 正是我想要的,但是我不能实例化 Foo,因为它是抽象类。我错过了什么吗?
  • 好吧,听起来您想为抽象基础提供一个工厂,以便在运行时动态选择 Foo 的哪个实现?
  • @mah,没有命名空间冲突,我可以链接两个库。我需要实例化 Foo 虽然

标签: c++ wrapper abstraction


【解决方案1】:

如果外部实现的所有方法名称都相似,则可以使用模板。让外部实现看起来像:

class FooX1
{
public:
    void Met1()
    {
        std::cout << "X1\n";
    }
};

class FooX2
{
public:
    void Met1()
    {
        std::cout << "X2\n";
    }
};

然后你可以使用几个变种。

变体 1. 您可以声明模板类型的成员并将所有调用封装到外部实现,即使在调用之前有一些准备。不要忘记在 ~Foo 析构函数中删除 impl

template<typename FooX>
class FooVariant1
{
public:
    FooVariant1()
    {
        impl=new FooX();
    }

    void Met1Wrapper()
    {
        impl->Met1();
    }
private:

    FooX *impl;
};

用法:

FooVariant1<FooX1> bar;
bar.Met1Wrapper();

变体 2。 您可以从模板参数继承。在这种情况下,您无需声明任何成员,而只需按名称调用实现的方法。

template<typename FooX>
class FooVariant2 : public FooX
{
};

用法:

FooVariant2<FooX1> bar;
bar.Met1();

使用模板的一个缺点是没有简单的方法可以在运行时更改实现。但作为回报,您会得到更优化的代码,因为类型是在编译时生成的,并且没有虚函数表,这会使程序变慢。

【讨论】:

  • 但是您仍然需要有一个地方来决定是否使用FooX1FooX2 实例化模板 - 这会带回#ifdefs 或类似的东西。不过,使用模板的想法不错
  • @Sergey 我花了一些时间来思考(!)发生了什么,但这就是我要做的事情
【解决方案2】:

如果您希望这 2 个实现在运行时共存,接口是可行的方法(例如,您可以使用工厂方法设计模式来实例化具体对象,就像 @n.m. 建议的那样)。

如果您可以在编译时决定您需要什么实现,您有多种选择:

  • 仍在使用界面。如果将来您在运行时需要这两种实现,这将允许轻松转换。

  • 使用预处理器指令。就 C++ 而言,这里没有错。这是一个纯粹的设计问题。

  • 将实现放在不同的文件中,并配置您的编译器以根据设置编译其中一个 - 这实际上类似于使用预处理器指令,但它更干净并且不会向您的代码添加垃圾(因为标志在解决方案/makefile/无论你的编译器使用什么)。

【讨论】:

    【解决方案3】:

    我唯一不满意的是将两个实现都包含在同一个源文件中。这可能会让人感到困惑。否则,这是预处理器标志擅长的事情之一,特别是如果您没有同时链接两个库。这就像支持多个操作系统一样。在所有情况下提供一致的接口,并将实现细节隐藏在其他地方。

    Foo 类型是否需要保存特定于每个库的任何信息?如果没有,你也许可以摆脱这个:

    #include "Foo.h"
    
    #if defined _FOOXTERNAL1_WRAPPER_
        #include "Foo_impl1.cpp"
    #elif defined _FOOXTERNAL2_WRAPPER_
        #include "Foo_impl2.cpp"
    #else
        #error "Warn about a missing define here"
    #endif
    

    这样您就不必为虚函数或继承而烦恼,并且仍然可以防止任何成员函数未实现。

    【讨论】:

      【解决方案4】:

      保留Foo 的摘要。提供一个工厂方法

      Foo* MakeFoo();
      

      分配FooImpl1FooImpl2 类型的新对象,并返回其地址。

      Wikipedia on Factory Method pattern.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-09
        • 2019-08-25
        • 2014-10-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多