【问题标题】:How to "uninclude" a header file?如何“取消包含”头文件?
【发布时间】:2012-01-16 18:16:09
【问题描述】:

我在AType.h 文件中有一个类,它在 AType.cpp 中实现。

# include "PrivateType.h"

class AType{
    private:
    int a, b, c;
    PrivateType varX;

    public:
    ...
};

我想在文件main.cpp 中使用AType 类,我需要包含AType.h,但我想避免在main.cpp 中包含PrivateType.h
我无法使用 malloc/new 创建 varX
main.cpp 必须在编译时知道 AType 的大小。

目前的解决方案:(不好)

1 - 创建一个程序来打印sizeof(AType)
2 - 更改标题:

# ifdef ATYPE_CPP
    # include "PrivateType.h"
#endif

class AType{
    private:
    # ifdef ATYPE_CPP
        int a, b, c;
        PrivateType varX;
    # else
        char data[ the size that was printed ];
    # endif

    public:
    ...
};

3 - AType.cpp 将以:

# define ATYPE_CPP
# include "AType.h"

编辑 1

有没有一种方法或工具可以自动将复杂的结构更改为 C 原始类型?
我不想打开头文件并找到结构。

如果 PrivateType 是:

struct DataType {
    float a, b;
};
class PrivateType {
    void* a;
    int b;
    short c;
    DataType x;

    ... functions
};

AType 将更改为:

class AType {
    int a, b, c;
    struct x {
        void* a;
        int b;
        short c;
        struct x2{
            float a, b;
        };
    };
};

我会分别处理复制/相等方法。
我使用 GCC 或 Clang。

编辑 2
新的解决方案?

它适用于 GCC。

1 - 获取 sizeof(AType)__alignof__(AType)
2 - 更改标题:

# ifdef ATYPE_CPP
    # include "PrivateType.h"
#endif

class AType{
    private:
    # ifdef ATYPE_CPP
        int a, b, c;
        PrivateType varX;
    # else
        char data[ 'the sizeof(AType)' ];
    # endif

    public:
    ...
}
# ifdef ATYPE_CPP
    ;
# else
    __attribute__ (( aligned( 'The __alignof__(AType)' ) ));
# endif

3 - 在 AType.cpp 中编写所有复制/相等方法。

会有用吗?

【问题讨论】:

  • 为什么不想包含PrivateType.h?您可以将类型粘贴到 Detail 命名空间中吗?
  • #unclude <iostream>? disusing namespace std;?
  • @JamesMcNellis 所说的是,在 C++ 中,将“私有”类型放在 namespace detail 中是惯用的,这表明它们只能由实现代码访问,而不是用户代码。
  • 我认为你最好使用 PIMPL 成语 - stackoverflow.com/questions/60570/…
  • @H2CO3:对齐是类型的基本属性,如果该位置针对变量的类型正确对齐,则您只能将变量安全地存储在内存位置。未修饰的 char 数组不一定满足这些要求。

标签: c++ c include header-files encapsulation


【解决方案1】:

您当前的方法将巧妙地和灾难性地失败。编译器必须始终看到该类的 same 声明。具体来说,考虑编译器为类AType 生成的相等或赋值运算符,而PrivateType 的正确定义不存在。编译器会错误地为 char 数组生成复制/相等方法。

你可以做的是转发声明你的私有类型:

class PrivateType;

class AType{
    private:
    int a, b, c;
    PrivateType *varX;

    public:
    ...
};

请注意,varX 现在是一个指针,指向尚未定义的类(当然,您必须自己分配/解除分配;智能指针类型可能会有所帮助)。在您的AType.cpp 中,您可以通过#include "PrivateType.h" 获得完整的定义,这样您就可以实际使用该类的成员了。

【讨论】:

  • 这违反了规定的约束:“我无法使用 malloc/new 创建 varX。”
  • 好吧,somebody 必须分配它。如果调用者不知道该怎么做,那么类本身将不得不承担责任。
  • @JamesMcNellis:然后同时使用:varX=malloc(sizeof(*varX)); new(varX) PrivateType;。显然AType::AType可以调用PrivateType::PrivateType
【解决方案2】:

您不能做您想做的事(因为您排除了动态分配),并且您的“解决方案”通常不起作用,即使您避免了其他人提到的编译器生成的特殊成员函数的问题。一个问题是类型不仅有大小,还有对齐方式。例如,你的真实类包含一个int,但是你的替换类只包含一个char 数组。现在在大多数平台上,int 具有对齐 4(即 int 必须位于 4 字节边界),而 char 具有对齐 1(它不能有任何其他对齐而不违反标准)。也就是说,一旦你尝试用你的替换定义创建一个对象,你就有可能让它错位,在最好的情况下会导致严重的减速,在最坏的情况下会导致程序崩溃(在绝对最坏的情况下,它将在您的测试中工作,但在实际使用时会失败)。

【讨论】:

    【解决方案3】:

    常见的解决方案是 Pimpl:创建一个包含公共类成员的结构/类。旧类只有一个成员:指向这个新结构/类的指针。观察:

    struct ATypeData;
    
    class AType
    {
    private:
        ATypeData *m_pData;
    
        public:
        ...
    };
    

    AType 的源文件将具有ATypeData 的实际定义,以及包含这些成员所需的任何标头。

    建议为m_pData 成员使用智能指针,这样您就不必拥有析构函数和复制构造函数以及所有这些好东西。

    【讨论】:

    • 这违反了规定的约束:“我无法使用 malloc/new 创建 varX。”
    • 但我不是用new 创建varX;我正在使用new 创建包含 varX 的对象。如果他根本不想要new,那就不一样了。
    • @NicolBolas:分配对象的行为在某种程度上不是分配子对象的行为?
    【解决方案4】:

    没有办法做你想做的事。现在像“不包含”这样的东西。
    但是,也许这些想法可能有用:

    1. 包括PrivateType.h,然后添加会阻止使用它的预处理器定义。类似于#define PrivateType dont use this!。如果这是在 AType.h 完成所有合法使用之后完成的,那么这些定义应该不会造成任何问题。

    2. 与您的解决方案类似,定义一个数组而不是 PrivateType。但是:
      一种。将其定义为void * 的数组。这将确保数组正确对齐(我假设没有类型比指针具有更严格的对齐要求)。
      湾。将大小定义为一个简单的常数。没有运行程序两次。
      C。在AType.c 中,验证大小是否与实际大小匹配。一种方法是定义两个无用的数组,一个大小为DEFINED_SIZE-sizeof(AType),另一个大小为sizeof(AType)-DEFINED_SIZE。如果存在差异,编译会因为数组的大小为负数而失败。
      d。更改AType 时,您必须手动更改>e DEFINED_SIZE。但如果你忘记了,你会得到一个很好的提醒。

    【讨论】:

      【解决方案5】:

      有办法,但我不建议这样做。在 AType.cpp 中,您有某种从每个 AType 对象到 PrivateType 的编译单元范围映射。

      您还可以拥有某种 PrivateType 对象池,然后您的 AType 将有一个指针(甚至可能是一个引用),它从池中指向该对象。当然,它将 AType 对象的数量限制为池中的数量。

      如果您不认为 new 也被禁止使用,那么最后您的选择是使用placement-new。在这种情况下,您有上面的结构,其中包含内存,还有一个 PrivateType * 成员指针。在构造函数中,您将使用placement new 在内存空间中创建对象,在析构函数中,您必须调用私有类型的析构函数。您的会员可以作为参考。

      PrivateType & varX;
      
      AType::AType : varX( *new(data)PrivateType )
      {
      }
      
      ~AType::Aype()
      {
          varX.~PrivateType();
      }
      

      这种模仿你现在拥有的东西,但使用了“邪恶”的新词......

      【讨论】:

        【解决方案6】:

        对于一个类或库来说,创建一堆类型是很常见的,这些类型除了库本身之外不会在其他任何地方使用。特别是,您不希望它们通过 malloc/new 等单独创建。

        当前 C++ 标准无法实现的理想解决方案是在全局范围内将类型指定为 private。当 C++20 引入模块时,可能是一种方式。

        今天通常做的是指定一个特殊的命名空间(通常是namespace detail)并将所有私有类型放在那里。一个不成文的规则是你的 header/lib 的用户不允许直接使用来自 detail 命名空间的任何东西。例如detail::PrivateType* foo = ... 被禁止。

        同时,这不会阻止您访问已由类/库公开提供的 detail::PrivateType 对象 - 这是一件好事。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-08-02
          • 2018-10-01
          • 2012-03-15
          • 1970-01-01
          • 1970-01-01
          • 2017-09-06
          • 2019-08-04
          • 2012-10-05
          相关资源
          最近更新 更多