1

我想避免重新编译包含公共头文件的所有内容,只是因为类定义的私有部分发生了更改。我正在研究 PIMPL 之外的其他选项。

这是我尝试过的:

我创建了一个包含 A 类的库:

A_p.h包含 A 类的私有部分

void PrivateMethod(int i);

公共头文件:

class A
{
public:
    A();
    virtual ~A();
    // other public members
private:
#ifdef A_PRIVATE
#include "A_p.h"
#endif
};

A.cpp

#define A_PRIVATE
#include "A.h"

A::A() {}
A::~A() {}
void A::PrivateMethod(int i) { }

然后,我创建了一个 Win32 控制台项目,其中包括公共标头 (Ah) 和针对 .lib 文件的链接。

一切似乎都有效,但我想知道一路上是否有任何陷阱。谁能详细说明这一点?

4

4 回答 4

6

“一切似乎都有效” -似乎是必不可少的。您只是遇到了未定义的行为。那是一个格式错误的程序——一个类定义在使用该类的编译单元中必须是相同的。

由于这是 UB,它似乎可以工作,但尝试virtual在私有部分声明一个方法,您很可能会遇到一些明显的问题。

于 2013-04-10T15:47:31.317 回答
1

抽象类允许您通过子类化抽象类来声明公共接口但具有私有数据和函数。

这将无法按照您描述的方式工作,因此在 C++ 标准中不受支持的一个关键原因是您提出的公共声明使得无法知道A. 公开声明没有透露私有数据需要多少空间。因此,只看到 public 声明的代码不能执行new A,不能为定义的数组分配空间,A也不能用指向 的指针进行算术运算A

还有其他问题,可能会以某种方式解决,例如指向虚函数成员的指针所在的位置。然而,这会导致不必要的并发症。

要创建一个抽象类,您至少要在该类中声明一个虚函数。定义虚函数= 0而不是函数体。这表示它没有实现,因此除了作为从它派生的类的子对象外,没有抽象类的对象。

然后,在单独的私有代码中,声明并定义一个B派生自A. 您将需要提供创建和销毁对象的方法,可能使用一个返回指针的公共“新”函数,A并通过调用一个可以看到声明的私有函数B和一个接受指针的公共“删除”函数来工作A并通过调用可以看到B.

于 2013-04-10T16:18:53.073 回答
1

有 3 种隐藏此类信息的好方法:

  1. 向前声明你的班级。这仅在它只是通过客户端代码(通过指针和/或引用)传递并且仅在您的库中使用时才有效。您的库首先需要提供工厂函数或类似函数来返回指针/引用,客户端永远不能调用newdelete

  2. 公开一个抽象基类,并再次提供工厂函数(实例化一个仅在您的库中可见的具体派生类)

  3. 使用粉刺

我也同意你应该重新考虑任何大到有必要隐藏它的类,但如果你真的不能分解它,那么这些是你的选择。


至于 ODR 违规如何以及为什么会在实践中破坏事情:

  • 库及其客户端代码对实例的大小可能有不同的看法。这可能会导致分配/解除分配等问题。
  • 他们还可以期待不同的数据成员偏移量、vtable 布局等。
    • PS。出于这些目的,内联方法算作客户端代码,因为它们不会通过放入新的动态库构建来更新
    • 聚苯乙烯。较新的优化器可能会在您不知情的情况下内联一些离线方法
于 2013-04-10T16:31:16.297 回答
0

正如所写的,在 C++ 中不可能有一个适当的部分类,在某些情况下这实际上很烦人。继承和 Pimpl 模式提供了一种替代方案,但每个对象都有一个指针的开销,这在 RAM 有限的嵌入式软件中可能很高。

为了解决这个问题,ISO 有一个官方的“部分类”提案:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0309r0.pdf

不幸的是,该提议被拒绝了。

于 2021-01-11T14:50:10.717 回答