【问题标题】:Hide class type in header在标题中隐藏类类型
【发布时间】:2011-04-01 07:58:43
【问题描述】:

我不确定这是否可能,但这里是:

我有一个库,其界面充其量是复杂的。不幸的是,它不仅是一个 3rd-party 库(而且太大而无法重写),我正在使用一些其他依赖于它的库。所以这个界面必须保持原样。

为了解决这个问题,我试图从本质上包装接口并将所有依赖项的接口捆绑到更少、更多逻辑的类中。那部分进展顺利,效果很好。大多数包装类都包含一个指向原始类之一的对象的指针。像这样:

class Node
{
public:
    String GetName()
    {
        return this->llNode->getNodeName();
    }

private:
    OverlyComplicatedNodeClass * llNode; // low-level node
};

我唯一的问题是次要问题。除了简化界面,我想删除链接到原始标题/库的要求。

这是第一个困难。如何以不需要包含原始标题的方式包装类?包装器将被构建为共享库 (dll/so),如果这样更简单的话。

原始类是指针,不用于任何导出函数(尽管它们在少数构造函数中使用)。

我玩弄了一些想法,包括预处理器的东西,例如:

#ifdef ACCESSLOWLEVEL
#    define LLPtr(n) n *
#else
#    define LLPtr(n) void *
#endif

这是丑陋的,充其量。它基本上可以满足我的需求,但我更喜欢那种混乱的真正解决方案。

某种指针类型的魔法起作用了,直到我遇到了一些使用共享指针的函数(某种提供引用计数的自定义 SharedPtr<> 类),更糟糕的是,一些特定于类的共享指针从基本SharedPtr 类(例如NodePtr)。

是否有可能以只需要包含我的标题以链接到我的动态库的方式包装原始库?不需要链接到原始库或从中调用函数,只是我的。我遇到的唯一问题是使用的类型/类。

这个问题可能不是很清楚。如果有帮助,我可以尝试清理它并添加更多代码示例。我并不真正担心任何性能开销或此方法的任何内容,只是尝试使其首先工作(过早的优化等等)。

【问题讨论】:

标签: c++ wrapper


【解决方案1】:

使用Pimpl(指向实现的指针)习语。如上所述,OverlyComplicatedNodeClass 是就您的库用户而言的实现细节。他们不应该知道这个类的结构,甚至它的名字。

当您使用 Pimpl 习惯用法时,您将类中的 OverlyComplicatedNodeClass 指针替换为指向 void 的指针。只有库作者需要知道void* 实际上是OverlyComplicatedNodeClass*。所以你的类声明变成:

class Node
{
public:
    String GetName();

private:
    void * impl; 
};

在您的库的实现中,使用指向执行实际工作的类的指针初始化 impl

my_lib.cpp

Node::Node()
: impl(new OverlyComplicatedNodeClass)
{
// ...
};

...您图书馆的用户永远不需要知道OverlyComplicatedNodeClass 的存在。

这种方法有一个潜在的缺点。所有使用impl 类的代码都必须在您的库中实现。如果它可以是内联的,则没有。这是否是一个缺点很大程度上取决于您的应用程序,因此请自行判断。

就您的班级而言,您确实在标题中有GetName() 的实现。必须将它移到库中,就像所有其他使用 impl 指针的代码一样。

【讨论】:

  • GetName() 的内联纯粹是为了可读性,表明我需要访问包装类的成员/方法。我的标头没有实现,所以这不是问题。无论如何,这一切都在一个共享库中,因此提供了另一层绝缘。我对这种方法的唯一问题是如何处理haredPtr<>,更重要的是具体的NodePtr 等?我并不完全熟悉 pimpl 的细节,但一些示例显示使用纯粹位于代码文件中的另一个类来管理类似的事情。这会是一个可行的解决方案吗?
  • pimpl 成语的某些用法可能会变得相当复杂,而且很难区分什么是 pimpl 和什么是其他东西。在实现文件中使用 pimpl 非常简单,只需在需要使用实现类时执行 reinterpret_cast<MyImplClass*>(impl) 即可。通常这被抽象为库本地的辅助函数,例如MyImplClass* get_impl(void* v) { return reinterpret_cast<MyImplClass*>(v); }
  • @peachykeen:如果我没有回答你的问题,请告诉我,我会再试一次。
  • 不用担心,一切正常。写起来有点痛苦,但到目前为止它工作得很好。谢谢。
【解决方案2】:

基本上,每次使用都需要一组单独的标题。一种用于构建 DLL,另一种仅具有导出的接口,完全没有提及封装的对象。您的示例如下所示:

class Node
{
public:
    String GetName();
};

如果您不介意混乱,可以使用预处理器语句在同一个物理文件中获取两个版本。

【讨论】:

  • 我在想这个。我在想使用#ifdef 应该能够处理它。会有任何与对象大小相关的副作用吗?例如,如果我的类 N 包含私有成员:一个指向 T 的 ptr 和一个 SharedPtr;在这些部分被预处理器隐藏的程序中使用它会有什么问题吗?
  • 对象大小仅在进行静态初始化时才成为一个因素,我不确定这对动态链接的类是否有意义。如果您需要静态对象,John 的 Pimpl 想法是不二之选。
猜你喜欢
  • 1970-01-01
  • 2014-06-27
  • 1970-01-01
  • 2019-06-23
  • 2013-09-22
  • 2012-03-05
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
相关资源
最近更新 更多