Проблема прямого объявления C ++ при вызове метода - PullRequest
4 голосов
/ 20 января 2009

У меня есть проблема, которая, я думаю, связана с предварительными заявлениями, но, возможно, нет.

Вот соответствующий код:

хиджры

#ifndef A_H_
#define A_H_

#include "B.h"

class A
{
    private:
        B b;

    public:
        A() : b(*this) {}

        void bar() {}
};

#endif /*A_H_*/

B.h

#ifndef B_H_
#define B_H_

#include "A.h"

class A;

class B
{
    private:
        A& a;

    public:
        B(A& a) : a(a) {}

        void foo() { /*a.bar();*/ } //doesn't compile
};

#endif /*B_H_*/

main.cpp

#include "A.h"

int main()
{
    A a;

    return 0;
}

Проблема, похоже, связана с вызовом A :: bar (). Программа успешно компилируется, пока я не попытаюсь вызвать этот метод, после чего я получаю две ошибки:

ошибка: недопустимое использование неполного типа ‘struct A’

ошибка: предварительное объявление "struct A"

Я предполагаю, что это потому, что A :: bar () еще предстоит определить или объявить, поскольку оба заголовка ссылаются друг на друга. Тем не менее, я пересылаю объявленный класс А и не знаю, что еще мне нужно сделать. Я новичок в C ++, поэтому, пожалуйста, прости меня. Я не мог найти ответ на этот вопрос нигде в Интернете. Как всегда, заранее спасибо!

Ответы [ 6 ]

9 голосов
/ 20 января 2009

У вас есть циклическая ссылка, поэтому вам нужно отделить B.h. Попробуйте что-то вроде:

B.h:

#ifndef B_H_
#define B_H_

// don't include A.h here!

class A;

class B
{
   private:
      A& a;

   public:
      B(A& a) : a(a) {}

      void foo();
};

#endif /*B_H_*/

B.cpp:

#include "B.h"
#include "A.h"

void B::foo() { a.bar(); } // now you're ok

Редактировать: объяснение, почему вам нужно разбить его на два файла:

Класс B содержит ссылку на A, который может быть так называемым неполным типом. Вы не можете вызывать какие-либо функции, потому что компилятор еще не знает, что это за хрень A - он просто знает, что это какой-то класс. Как только вы включите A.h (в файл .cpp), A будет полным типом, и вы сможете делать с ним все, что захотите.

Вы не можете хранить все это в одном заголовочном файле, потому что получите циклическую ссылку. Вы предотвращаете бесконечный цикл со своими включенными охранниками, но получаете то, что вам не нужно. Посмотрите, чем заканчивается компилятор при компиляции main.cpp, как это было раньше:

// #include "A.h" ==>
#define A_H_

// #include "B.h" ==>
#define B_H_

// #include "A.h" ==> nothing happens! (since A_H_ is already defined)

class A;

class B {
private:
    A& a;

public:
    B(A& a) : a(a) {}

    void foo() { a.bar(); } // <-- what the heck is A here?
                            //     it's not defined until below
};

class A {
private:
   B b;

public:
   A() : b(*this) {}

   void bar() {}
};

int main() {
    A a;
    return 0;
}
1 голос
/ 20 января 2009

Если вы действительно хотите, чтобы B :: foo был встроенным, вы можете выполнить реализацию в B.h, хотя я бы не рекомендовал это.

B.h:

#ifndef B_H_
#define B_H_

// Forward declaration of A.
class A;

class B
{
private:
    A& a;

public:
    B(A& a) : a(a) {}

    void foo();
};

// Include definition of A after definition of B.
#include "A.h"

inline void B::foo()
{
    a.bar();
}

#endif /*B_H_*/

хиджра:

// Include definition of B before definition of A.
// This must be done before the ifndef A_H_ include sentry,
// otherwise A.h cannot be included without including A.h first.
#include "B.h"

#ifndef A_H_
#define A_H_

class A
{
private:
    B b;

public:
    A() : b(*this) {}

    void bar() {}
};

#endif /*A_H_*/
1 голос
/ 20 января 2009

Как уже упоминали несколько других, круговая ссылка является вашей проблемой. Еще одна фраза для этого будет «взаимозависимость». Однако вместо того, чтобы пытаться найти правильный синтаксис, чтобы заставить ваше приложение компилироваться и запускаться (я предполагаю, что реальная проблема существует в программе, немного более продвинутой, чем вы опубликовали), я бы посоветовал вам атаковать проблему с помощью объекта ориентированная на дизайн точка зрения.

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

Допустим, у нас есть два класса: Data и DataAnalyzer

Данные содержат ссылку на DataAnalyzer (используется для анализа данных), а DataAnalyzer содержит ссылку на данные (данные, которые будут проанализированы) - взаимозависимость! Чтобы устранить эту зависимость, мы извлекаем интерфейс (в C ++, чистый виртуальный класс) из DataAnalyzer, который определяет публичные методы / атрибуты, необходимые для DataAnalyzer. Это может выглядеть примерно так:

class IAnalyzer
{
public:
    virtual void Analyze () = 0;
};

Когда мы определяем DataAnalyzer, мы делаем это следующим образом:

class DataAnalyzer : public IAnalyzer
{
public:
    DataAnalyzer (Data* data);

    virtual void Analyze (); // defined in DataAnalyzer.cpp
};

А данные выглядят так:

class Data
{
public:
    Data ();

    IAnalyzer* Analyzer;
};

Где-то, в вашем классе контроллеров, у вас может быть что-то вроде:

void main ()
{
    Data*  data = new Data ();
    data->Analyzer = new DataAnalyzer (data);
}

Теперь Data стоит сам по себе (насколько ему известно, IAnalyzer не требует ссылки на Data), и только DataAnalyzer зависит от Data. Если вы хотите продолжать работу, вы можете продолжить удаление зависимости DataAnalyzer от Data, но ради простой разрыва взаимозависимости этого должно быть достаточно.

Предупреждение: Я не проверял этот код при компиляции, поэтому для правильной компиляции и запуска может потребоваться небольшая настройка.

Удачи!

1 голос
/ 20 января 2009

Строка #include<file.h> просто заменит строку с содержимым file.h. Поэтому, когда компьютер пытается скомпилировать ваш main.cpp, он соберет все вместе, что выглядит следующим образом. В месте, где вы хотите использовать A :: bar (), он не был определен.

// result from #include "A.h"

#ifndef A_H_
#define A_H_

// Now, #include "B.h" in A.h will get you the following
#ifndef B_H_
#define B_H_

// here you include "A.h" again, but now it has no effect
// since A_H_ is already defined

class A;

class B
{
    private:
            A& a;
    public:
            B(A& a) : a(a) {}
            // Oops, you want to use a.bar() but it is not defined yet
            void foo() { /*a.bar();*/ } 
};

#endif /*B_H_*/

class A
{
    private:
            B b;
    public:
            A() : b(*this) {}
            void bar() {}
};
#endif /*A_H_*/

// now this is your main function
int main()
{
    A a;
    return 0;
}
0 голосов
/ 20 января 2009

Чтобы добавить к другому ответу (циклическая ссылка, которая является правильным ответом), если вы исходите из C # / Java, следует понимать, что C ++ отличается тем, что файлы анализируются по порядку (а не рассматриваются как целое). ). Поэтому вы должны быть осторожны, чтобы убедиться, что все определено до того, как оно будет использовано, в фактическом порядке, в котором файлы включены (и / или, в зависимости от ситуации, разделены на файлы .cpp).

0 голосов
/ 20 января 2009

В B.h вы включаете A.h, а также вперед, объявляя A.

Вам необходимо разделить B.h на B.h и B.cpp или удалить предварительную декларацию.

PS У вас также есть круговая зависимость. A.h включает в себя B.h, и наоборот. Ваши охранники ловят проблему, хотя;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...