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.com/   -  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: Съгласен съм с бележката ви в по-голямата си част, но проблемът беше, че казахте „нужда“, когато всъщност не е необходимо. Предположих, че сте го казали, защото много хора смятат, че не можете да преминете по стойност, ако всичко, което имате, е предварителна декларация, което е невярно. Освен това има моменти, когато трябва да преминете по стойност. Понякога все пак трябва да направите копие на обекта и най-доброто място да направите това е в списъка с аргументи, за да позволите семантика на преместване (или евентуално копиране на elision) в случаите, когато случайно получите 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