2013-04-10 69 views
1

我想避免重新编译包含公共头文件的所有内容,仅仅是因为类定义的私有部分发生了某些变化。我正在调查PIMPL旁边的其他选项。C++中的部分类

这是我的尝试:

我创建了一个包含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) { } 

我然后创建一个包括公头(A.H)和链接针对的.lib文件的Win32控制台项目。

一切似乎都奏效,但我想知道任何一路上的陷阱。任何人都可以详细说明这一点?

+6

“我读到PIMPL,但我在寻找一种方式,以避免它。”为什么? – 2013-04-10 15:46:28

+3

您违反了ODR并导致UB。 – PlasmaHH 2013-04-10 15:47:30

+0

那么如果你改变私人部分会发生什么? – juanchopanza 2013-04-10 15:49:26

回答

1

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

一个关键的原因,这不会按照你描述的方式工作,因此在C++标准中不被支持,因为你提出的公开声明使得不可能知道A的大小。公开声明并未透露私人数据需要多少空间。因此,仅看到公开声明的代码不能执行new A,不能为A的数组的定义分配空间,并且不能使用指向A的指针进行算术运算。

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

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

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

+0

现在,这是一个不使用这种结构的好理由。在lib和exe中不同。 – 2013-04-10 16:29:16

6

“一切似乎工作” - 似乎有必要的。你只是遇到未定义的行为。这是一个不合格的程序 - 在使用该类的编译单元中,类定义必须相同。

由于这是UB,它似乎可以正常工作,但是尝试在私人区域声明一个virtual方法,您很可能会遇到一些可见问题。

+0

是否有任何方法来确保二进制兼容性? – 2013-04-10 15:48:25

+2

@WouterHuysentruit否,因为这种方法根本上是错误的。 – 2013-04-10 15:48:47

+3

@WouterHuysentruit加上,你**应该**使用PIMPL惯用语 - 这个权利这里是它首先存在的原因之一。 – 2013-04-10 15:49:23

1

有3种躲好这类信息的方式:

  1. 前瞻性声明类。这只适用于通过客户端代码传递(通过指针和/或引用),并且只有在库中使用。你的图书馆需要提供工厂函数或类似的回摆在首位的指针/引用,客户端可以永远不会调用newdelete

  2. 暴露的抽象基类,并再次提供工厂函数(实例一个具体的派生类中仅可见

  3. 使用PIMPL内部资料库)

我还同意,你应该reconsi如果任何职业如此之大以至于隐藏它是必要的,但如果你真的无法分手,那么这些就是你的选择。


至于如何&为什么ODR违反打破在实践中的东西:

  • 库及其客户端代码可以有关于实例的大小不同的意见。这会导致分配/解除分配等等
  • 它们也可以期望不同的数据成员偏移,虚函数表的布局等
    • PS问题。内联方法会作为客户端代码用于这些目的,因为它们不会通过放入新的动态库版本来更新。
    • PPS。较新的优化可能能够内联一些乱线方法,不用您知道