Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/149.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++中的部分类_C++_Partial Classes_Binary Compatibility - Fatal编程技术网

C++中的部分类

C++中的部分类,c++,partial-classes,binary-compatibility,C++,Partial Classes,Binary Compatibility,我希望避免重新编译包含公共头文件的所有内容,因为类定义的私有部分发生了更改。我正在调查PIMPL之外的其他选择 这就是我所尝试的: 我创建了一个包含类a的库: A_p.h包含A类的私有部分 void PrivateMethod(int i); A.h公共头文件: class A { public: A(); virtual ~A(); // other public members private: #ifdef A_PRIVATE #include "A_p.h" #

我希望避免重新编译包含公共头文件的所有内容,因为类定义的私有部分发生了更改。我正在调查PIMPL之外的其他选择

这就是我所尝试的:

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

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

void PrivateMethod(int i);
A.h公共头文件:

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

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


一切似乎都很顺利,但我不知道在这过程中会不会有什么陷阱。有人能详细说明一下吗?

一切似乎都在运转——似乎有必要。您正在经历未定义的行为。这是一个格式错误的程序——在使用该类的编译单元中,类定义必须相同


由于这是UB,它看起来可以工作,但是尝试在私有部分声明一个虚拟方法,您很可能会遇到一些可见的问题。

一切似乎都正常-似乎有必要。您正在经历未定义的行为。这是一个格式错误的程序——在使用该类的编译单元中,类定义必须相同


由于这是UB,它看起来可以工作,但是尝试在私有部分声明一个虚拟方法,您很可能会遇到一些可见的问题。

抽象类允许您通过对抽象类进行子类化来声明一个公共接口,但具有私有数据和函数

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

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

要生成抽象类,您必须在类中声明至少一个虚拟函数。虚拟函数定义为=0,而不是函数体。这意味着它没有实现,因此抽象类中不能有对象,除非作为派生自它的类的子对象


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

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

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

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

要生成抽象类,您必须在类中声明至少一个虚拟函数。虚拟函数定义为=0,而不是函数体。这意味着它没有实现,因此抽象类中不能有对象,除非作为派生自它的类的子对象


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

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

只能向前声明您的类。仅当它通过指针和/或引用传递给客户机代码,并且仅在库中使用时,它才起作用。您的库首先需要提供工厂函数或类似函数才能返回指针/引用,客户端永远不能调用new或delete

公开抽象基类,并再次提供工厂函数,该工厂函数实例化仅在库中可见的具体派生类

使用pimpl

我还想 请注意,您应该重新考虑任何大到需要隐藏它的类,但如果您真的无法将其拆分,则这些是您的选择

关于违反ODR的行为在实践中破坏事物的方式和原因:

对于实例的大小,库及其客户端代码可能有不同的意见。这可能会导致分配/解除分配等问题。 它们还可以预期不同的数据成员偏移、vtable布局等。 出于这些目的,内联方法被视为客户机代码,因为它们不会通过放入新的动态库构建来更新 PPS。较新的优化器可能能够在您不知道的情况下内联一些离线方法
有3种隐藏此类信息的好方法:

只能向前声明您的类。仅当它通过指针和/或引用传递给客户机代码,并且仅在库中使用时,它才起作用。您的库首先需要提供工厂函数或类似函数才能返回指针/引用,客户端永远不能调用new或delete

公开抽象基类,并再次提供工厂函数,该工厂函数实例化仅在库中可见的具体派生类

使用pimpl

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

关于违反ODR的行为在实践中破坏事物的方式和原因:

对于实例的大小,库及其客户端代码可能有不同的意见。这可能会导致分配/解除分配等问题。 它们还可以预期不同的数据成员偏移、vtable布局等。 出于这些目的,内联方法被视为客户机代码,因为它们不会通过放入新的动态库构建来更新 PPS。较新的优化器可能能够在您不知道的情况下内联一些离线方法

编写的,在C++中,一个适当的粒子类是不可能的,在某些情况下,这实际上是令人讨厌的。 继承和Pimpl模式提供了另一种选择,但每个对象都有一个指针的开销,这在RAM有限的嵌入式软件中可能会很高

为了解决这个问题,向ISO提交了一份正式的部分课程提案:


遗憾的是,这个建议被否决了。

书面的,C++中不可能有适当的粒子类,在某些情况下这是令人讨厌的。 继承和Pimpl模式提供了另一种选择,但每个对象都有一个指针的开销,这在RAM有限的嵌入式软件中可能会很高

为了解决这个问题,向ISO提交了一份正式的部分课程提案:



不幸的是,该提案被拒绝。

我读到了PIMPL,但我正在寻找一种避免的方法。为什么?您违反了ODR并导致了UB。那么,如果您更改了私有部分,会发生什么呢?PIMPL是您问题的正确解决方案。为什么要在已知和理解的解决方案存在的情况下创建新的习惯用法。其他开发人员将看到您的代码,并知道PIMPL的进展情况,而下一个看到您的代码并使用您的解决方案的人将感到困惑。@LuchianGrigore:“避免”在这里是一个大词,假设我们正在检查可能的替代方案。我读过PIMPL,但正在寻找一种方法来避免它。为什么?您违反了ODR并导致了UB。那么,如果您更改了私有部分,会发生什么呢?PIMPL是您问题的正确解决方案。为什么要在已知和理解的解决方案存在的情况下创建新的习惯用法。其他开发人员会看到你的代码,知道PIMPL的情况,而下一个看到你的代码使用你的解决方案的人只会感到困惑。@LuchianGrigore:“避免”在这里是一个大词,比如说我们正在检查可能的替代方案。难道没有办法确保二进制兼容性吗?@WouterHuysentruit no,因为这种方法从根本上是错误的。@WouterHuysentruit plus,您应该使用PIMPL习惯用法-这就是它存在的原因之一。我想以Qt为例。根据4.8.3头文件编译的基于Qt的程序仍能在具有Qt4.8.4库的系统上运行。而且他们确实向现有类添加了新方法。但在某种程度上,它们似乎保留了二进制兼容性…@LuchianGrigore IMHO,PIMPL有一个缺点——堆分配。堆栈上分配的小对象导致堆分配。这就是为什么可选而不仅仅是值语义指针。有像fast pimpl这样的方法,但它显然一点也不好=没有任何方法可以确保二进制兼容性吗?@WouterHuysentruit否,因为这种方法从根本上是错误的。@WouterHuysentruit plus,您应该使用PIMPL习惯用法——这就是它首先存在的原因之一。我想以Qt为例。根据4.8.3头文件编译的基于Qt的程序仍能在具有Qt4.8.4库的系统上运行。而且他们确实向现有类添加了新方法。但在某种程度上,它们似乎保留了二进制兼容性…@LuchianGrigore IMHO
,PIMPL有一个缺点-堆分配。堆栈上分配的小对象导致堆分配。这就是为什么可选而不仅仅是值语义指针。有一些方法像fast pimpl,但它显然一点也不好=现在,这是一个不使用这种结构的好理由。类的大小在lib和exe中确实是不同的。现在,这就是不使用这种结构的一个很好的理由。类的大小在lib和exe中确实是不同的。
#define A_PRIVATE
#include "A.h"

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