C++ header 依赖传播 - 如何限制它?

C++ header dependency propagation - how to limit it?

在用 C++ 构建 class 时,我反复遇到两难境地。我想构建 class 而不在外部传播其内部依赖项。

我知道我有如下选项:

  1. 使用 pimpl 习语
  2. 在header
  3. 中使用前向声明和仅引用或智能指针
// header

class Forwarded;    // I don't want to include Forwarded.h to not propagete it 

class MyNewClass {
private:
   std::unique_ptr<Forwarded> mForwareded;
   void method1UsesForwarded();
   void method2UsesForwarded();
public:
   void doSomeAction();
};
// cpp
#include "Forwarded.h"
void MyNewClass::doSomeAction() {
   method1UsesForwarded();
   method2UsesForwarded();
}

void MyNewClass::method1UsesForwarded() { /* implementation */ }
void MyNewClass::method2UsesForwarded() { /* implementation */ }
  1. 创建另一个 class 或辅助文件,使用我不想进一步传播的那些文件
// header

class MyNewClass {
public:
   void doSomeAction();
};

// cpp
#include "helper.h"
void MyNewClass::doSomeAction() {
    Forwarded f;
    method1UsesForwarded(f);
    method2UsesForwarded(f);
}
// helper.h
#include "Forwarded.h"
void method1UsesForwarded(Forwarded & f);
void method2UsesForwarded(Forwarded & f);
// helper.cpp
#include "helper.h"
void method1UsesForwarded(Forwarded & f) {
    //implemntation
}
void method2UsesForwarded(Forwarded & f) {
    //implemntation
}

还有其他选择吗?我不喜欢上述任何解决方案,因为它们会增加一些额外的复杂性。对我来说最好的选择是创建 Forwarded 作为普通私人成员,并且以某种方式不进一步传播它:-)

潜在的问题是 MyNewClass 的消费者需要知道它有多大(例如,如果它需要在堆栈上分配),所以需要知道所有成员才能正确计算尺寸。

我不会讨论您已经描述的模式。根据您的 use-case.

,还有一些可能会有所帮助
1。接口

创建一个 class 仅使用公开的方法和一个工厂函数来创建一个实例。

优点:

  • 完全隐藏所有私有成员

缺点:

  • 需要堆分配
  • 很多虚函数调用

Header:

class MyClass {
public:
  static MyClass* create();

  virtual ~MyClass() = default;
  virtual void doSomeAction() = 0;
};

来源:

class MyClassImpl : public MyClass {
public:
  void doSomeAction() override { /* ... */ }

private:
  void method1UsesForwarded() { /* ... */ }
  void method2UsesForwarded() { /* ... */ }

  Forwarded f;
};

MyClass* MyClass::create() {
  return new MyClassImpl();
}

2。子class 实施

这有点类似于接口,优点是它摆脱了虚函数。

优点:

  • 完全隐藏所有私有成员

缺点:

  • 需要堆分配

Header:

class MyClass {
public:
  static MyClass* create();

  // only the destructor needs to be virtual now
  virtual ~MyClass() = default;

  void doSomeAction();

private:
  MyClass() = default;
};

来源:

class MyClassImpl : public MyClass {
public:
  void method1UsesForwarded() { /* ... */ }
  void method2UsesForwarded() { /* ... */ }

  Forwarded f;
};

void MyClass::doSomeAction() {
  // e.g.:
  static_cast<MyClassImpl*>(this)->method1UsesForwarded();
  // ...
}

MyClass* MyClass::create() {
  return new MyClassImpl();
}

3。 Pre-allocating space 私人会员

如果您不介意一些手动工作,您可以计算出私有成员需要多少 space,并在基础 class 中为它提供足够大的缓冲区。

优点:

  • 完全隐藏所有私有成员
  • 不需要堆分配

缺点:

  • 您需要手动检查需要多大的缓冲区
  • 使用不同的编译器/不同的编译器版本时,所需的大小可能会改变

Header:

class MyClass {
public:
  MyClass();
  ~MyClass();
  void doSomeAction();
private:
  void method1UsesForwarded();
  void method2UsesForwarded();

  struct MyClassMembers* getMembers();
  
  /*
    here you need to enter the size and alignment requirements
    of your private data buffer.
  */
  std::aligned_storage_t<8, 4> m_members;
};

来源:

struct MyClassMembers {
    int i = 0;
    int j = 0;
};

MyClass::MyClass() {
  static_assert(sizeof(m_members) >= sizeof(MyClassMembers), "size too small!");
  static_assert(alignof(m_members) >= alignof(MyClassMembers), "alignment too small!");

  new (&m_members) MyClassMembers();
}

MyClass::~MyClass() {
    getMembers()->~MyClassMembers();
}

MyClassMembers* MyClass::getMembers() {
  return std::launder(reinterpret_cast<MyClassMembers*>(&m_members));
}

void MyClass::doSomeAction() {
  method1UsesForwarded();
  /* ... */
}

void MyClass::method1UsesForwarded() {
  /* ... */
  std::cout << getMembers()->i << std::endl;
}


void MyClass::method2UsesForwarded() {
  /* ... */
}

这种方法的一个问题是您需要为数据结构提供正确的大小和对齐方式(在本例中 MyClassMembers)。
您可以通过尝试使用不同的值进行编译直到它起作用来观察它( static_asserts 确保它不会在不满足要求的情况下进行编译),或者编写一个简短的函数来为您打印出正确的值:

struct MyClassMembers {
  int i;
  int j;
};

int main(int argc, char* argv[]) {
  std::cout << sizeof(MyClassMembers) << std::endl;
  std::cout << alignof(MyClassMembers) << std::endl;
}

我建议使用私有继承。 通过这样做,它使基础 class 的所有成员函数在继承的 class 中成为私有的(除了在构造函数中,在构造函数分配和初始化器完成之前没有隐藏)。

如果这样做,则无法从外部代码调用基础 class 成员。 Public 和基 class 的受保护成员在派生 class 中是私有的。基 class 的私有成员完全无法被派生 class 访问。

妥协是受保护的继承,实际上很少使用,因为它确实只有小众用途。受保护的继承使 public 和基 class 的受保护成员在派生 class 中受到保护,将它们从外部代码中隐藏起来,但将访问向下传播到进一步派生的 classes 中。

您确实必须规划出 class 层次结构,以了解派生级别可以访问哪些混合了 public、受保护和私有成员的功能。

如果您想确保不会在派生 class。这与大多数人的做法相反;他们通常希望在派生的 class.

中包含基本 class 函数作为 public