Проблема с прямым объявлением C ++ при вызове метода

StackOverflow https://stackoverflow.com/questions/460281

Вопрос

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

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

A.h

#ifndef A_H_
#define A_H_

#include "B.h"

class A
{
    private:
        B b;

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

        void bar() {}
};

#endif /*A_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’

ошибка:прямое объявление ‘структуры A’

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

Это было полезно?

Решение

У вас есть циклическая ссылка, поэтому вам нужно разделить 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;
}

Другие советы

Линия #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;
}

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

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

Допустим, у нас есть два класса:Данные и анализатор данных

Данные содержат ссылку на DataAnalyzer (используется для анализа данных), а DataAnalyzer содержит ссылку на Data (данные, подлежащие анализу) - взаимная зависимость!Чтобы устранить эту зависимость, мы извлекаем интерфейс (на 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);
}

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

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

Удачи!

Если вы действительно хотите, чтобы B::foo был встроенным, вы могли бы выполнить реализацию в 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_*/

A.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_*/

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

Вам нужно разделить B.h на B.h и B.cpp , или удалить прямое объявление.

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top