Как объявить классы в разных файлах C ++ - PullRequest
1 голос
/ 04 мая 2020

Я пытался выяснить, как перенаправить объявление классов в несколько файлов в C ++. Я был в состоянии успешно сделать это с двумя классами, но выполнение этого во многих классах озадачило меня. Я попытался объявить все классы в заголовочном файле и включить их во все классы, но я получил ошибку в неполных классах. Я изучил это, но все решения казались мне необоснованными или смущали меня. Можете ли вы объяснить хороший способ сделать это.

Если это поможет, вот несколько примеров кода проблемы, которая конкретно возникла. Класс A включен в класс C, но класс A также включает в себя класс B, который содержит класс C, что приводит к ошибке, поскольку класс C не был объявлен.

Объявление форварда класса

//Tried "Class Name {};" but that also failed
Class A;
Class B;
Class C;

Класс A

//Class A
#include "B.h"

class A{
 private:
  B b;
 public:
  void SetBInt(int set) {b.SetInt(set);}
}

Класс B

//Class B
#include "C.h"
class B{
 private:
  C c;
  int i = 0;
 public:
  void SetInt(int set){i = set;}
}

Класс C

//Class C
#include "A.h"
class C{
 private:
  int i = 0;
 public:
  void SetInt(int set){i = set;}
}

1 Ответ

2 голосов
/ 04 мая 2020

Вы должны избегать циклических зависимостей. Одним из распространенных способов избежать их является предварительное объявление типа и отсутствие в заголовке каких-либо экземпляров или вызовов функций этого типа.

Это делается путем изменения экземпляров, таких как B b, в ссылки. или указатели: B* b. Вы также должны переместить любой код, который использует b, в файл CPP, чтобы вы могли #include "B.h" до его использования.

Например, вместо

// we'll remove this header's dependency on B shortly
#include "B.h"

class A
{
  B b;
public:
  A() { b.SetInt(0); }
};

Вы делаете что-то вроде этого:

class B;

class A
{
  B *b; // *pointers and &references to a predeclared class are fine.
public:
  A();
  ~A();
};

... и затем в A. cpp:

#include "A.h" // ALWAYS include your own header first
#include "B.h"

A::A()
{
  // we didn't need to know how big B was until now, when we're about to make one and
  // run its constructor.  We'll also need to know what functions are available, and the
  // types of all its parameters (or lack of parameters).
  b = new B();
  b->SetInt(0);
}

A::~A()
{
  delete b; // for every "new" there must be a "delete"
}

Да, это было бы (намного) лучше с std::unique_ptr<B> b вместо необработанного указателя. С умным указателем нам не нужен явный деструктор. Я просто не хотел бросать слишком много потенциально новых вещей в кого-то, кто еще учится.

В качестве альтернативы, вы можете передать ссылку в конструктор и использовать список инициализатора члена (вы можете устанавливать ссылки только тогда, когда они ' перестроен). Это вызывает проблемы с «продолжительностью жизни объекта». Если экземпляр B, который вы передадите во время строительства, будет уничтожен до того, как с ним будет завершен A, ваша программа будет жестоко взламывать sh (если вам повезет: таким образом, у вас будет хорошее представление о том, в чем проблема это) в следующий раз, когда он используется. Лучше всего избегать этого, пока у вас не будет значительно больше опыта под вашим поясом.

...