C++: невозможно передать экземпляр объекта другому классу из-за цикла #include

Скажем, у меня есть 2 класса:

// a.h
#ifndef A_H
#define A_H

#include "b.h"

class A {
  public: void a() {
    B* b = new B(this);
  }
}

#endif


// b.h
#ifndef B_H
#define B_H

#include "a.h"

class B {
  public: B(A* a) {
     // ...
  }
}

#endif

Этот код вызовет ошибку A has not been declared, потому что класс A ссылается на класс B, который, в свою очередь, ссылается на класс A, который еще не был объявлен в a.h.

Итак, как я могу передать экземпляр A в B?


person Alex    schedule 25.04.2012    source источник
comment
В коде есть более вопиющие ошибки (#defines, new B…).   -  person Konrad Rudolph    schedule 25.04.2012
comment
Так много дубликатов: google.ru/   -  person Ben Voigt    schedule 25.04.2012
comment
@KonradRudolph, почему защита включения использует #define как ошибку? Альтернативой может быть #pragma once, но это (хотя и широко поддерживается и имеет преимущества) не является стандартным, не так ли? Что было бы наилучшей практикой?   -  person Péter Török    schedule 25.04.2012
comment
@Péter Код был отредактирован, в исходной версии использовались A и B в качестве имен включаемых охранников…   -  person Konrad Rudolph    schedule 25.04.2012


Ответы (3)


Тебе надо

  1. измените параметр передачи по значению конструктора B на указатель или ссылку*
  2. переместите определение B::B в файл cpp (это устраняет любую прямую зависимость от определения A и позволяет вам
  3. объявить вперед A в B.h :

    // b.h
    #ifndef B_H
    #define B_H
    
    class A;
    
    class B {
      public: B(const A& a) {
         // ...
      }
    }
    
    #endif
    
    // b.cpp
    
    #include "b.h"
    #include "a.h"
    
    public B::B(const A& a) {
      // ...
    }
    

Обратите внимание, что я добавил квалификатор const для параметра конструктора A&, так как я предполагаю, что вы не хотите изменять a в конструкторе B.

Конечно, вы также можете сыграть наоборот, переместив определение A::a и соответствующий #include "b.h" в a.cpp.

*Вам, скорее всего, следует сделать это в любом случае, так как маловероятно, что вы хотите передать туда объект A по значению. Передача объекта по значению означает неявное создание копии объекта и помещение ее в стек. Какой

  • означает, что изменения, внесенные в объект параметра, не повлияют на исходный объект, что обычно не соответствует вашим ожиданиям, поэтому является источником ошибок,
  • включает создание и удаление временного,
  • обычно потребляет гораздо больше памяти, чем ссылка/указатель,
  • открывает дверь для ошибок нарезки объектов.
person Péter Török    schedule 25.04.2012
comment
Это работает, спасибо. Но когда я вызываю какой-то метод класса A в B, я получаю invalid use of incomplete type A и предварительное объявление struct A. - person Alex; 25.04.2012
comment
И да, конечно, я хотел передать ссылку, я обновил свой пост. - person Alex; 25.04.2012
comment
@Alex, покажите нам соответствующий код, отметив, находится ли он в файле .h или .cpp. - person Péter Török; 25.04.2012
comment
@Alex, A* в вашем обновленном коде — это указатель, а не ссылка на A. Последний будет A&. - person Péter Török; 25.04.2012
comment
@PéterTörök: Я согласен с вашей заметкой по большей части, однако проблема заключалась в том, что вы сказали «нужно», хотя на самом деле в этом нет необходимости. Я предположил, что вы сказали это, потому что многие люди думают, что вы не можете передавать по значению, если все, что у вас есть, это предварительное объявление, что неверно. Кроме того, бывают случаи, когда вы должны пройти по значению. Иногда вам все равно нужно сделать копию объекта, и лучше всего это сделать в списке аргументов, чтобы разрешить семантику перемещения (или, возможно, исключение копирования) в тех случаях, когда вы получаете rvalue. - person Benjamin Lindley; 25.04.2012
comment
Извини, я виноват. Забыл включить a.h в b.cpp. Еще раз большое спасибо! - person Alex; 25.04.2012

Вы перемещаете реализацию в отдельные файлы. Таким образом, вам не нужно включать B.h внутри A.h:

// a.h
class A {
public: 
    void a();
};

// b.h
#include "a.h"
class B {
public: 
    B(A a) { }
};

// a.cpp
#include "a.h"
#include "b.h"
void A::a(){
    B* b = new B(*this); //B constructor doesn't take a pointer
}
person Luchian Grigore    schedule 25.04.2012

вы объявляете класс вперед, используя синтаксис

class A;

в вашем файле b.h этого достаточно, чтобы сообщить компилятору, что существует класс с именем A, на который у вас будут ссылки. Он не позволяет вам получить доступ к внутренностям этого класса (например, вы не сказали ему, какие у него члены или насколько он велик), но его достаточно для использования в качестве указателя или ссылки.

Другая вещь, которую вы можете сделать, это взглянуть на свой дизайн и попытаться удалить такие циклические зависимости.

person gbjbaanb    schedule 25.04.2012