Устранить ошибки сборки из-за циклической зависимости между классами - PullRequest
310 голосов
/ 09 марта 2009

Я часто нахожусь в ситуации, когда я сталкиваюсь с множественными ошибками компиляции / компоновщика в проекте C ++ из-за некоторых неудачных дизайнерских решений (принятых кем-то другим :)), которые приводят к круговым зависимостям между классами C ++ в разных заголовочных файлах (может быть и в том же файле) . Но, к счастью (?), Это происходит не так часто, чтобы я мог вспомнить решение этой проблемы в следующий раз, когда это случится снова.

Так что в целях легкого отзыва в будущем я собираюсь опубликовать репрезентативную проблему и решение вместе с ней. Лучшие решения, конечно, приветствуются.


  • A.h

    class B;
    class A
    {
        int _val;
        B *_b;
    public:
    
        A(int val)
            :_val(val)
        {
        }
    
        void SetB(B *b)
        {
            _b = b;
            _b->Print(); // COMPILER ERROR: C2027: use of undefined type 'B'
        }
    
        void Print()
        {
            cout<<"Type:A val="<<_val<<endl;
        }
    };
    

  • B.h

    #include "A.h"
    class B
    {
        double _val;
        A* _a;
    public:
    
        B(double val)
            :_val(val)
        {
        }
    
        void SetA(A *a)
        {
            _a = a;
            _a->Print();
        }
    
        void Print()
        {
            cout<<"Type:B val="<<_val<<endl;
        }
    };
    

  • main.cpp

    #include "B.h"
    #include <iostream>
    
    int main(int argc, char* argv[])
    {
        A a(10);
        B b(3.14);
        a.Print();
        a.SetB(&b);
        b.Print();
        b.SetA(&a);
        return 0;
    }
    

Ответы [ 11 ]

0 голосов
/ 05 июля 2018

К сожалению, во всех предыдущих ответах отсутствуют некоторые детали. Правильное решение немного обременительно, но это единственный способ сделать это правильно. И он легко масштабируется, обрабатывает и более сложные зависимости.

Вот как вы можете это сделать, точно сохранив все детали и удобство использования:

  • решение точно такое же, как первоначально предполагалось
  • встроенные функции по-прежнему встроены
  • пользователи A и B могут включать A.h и B.h в любом порядке

Создайте два файла, A_def.h, B_def.h. Они будут содержать только определения A и B:

// A_def.h
#ifndef A_DEF_H
#define A_DEF_H

class B;
class A
{
    int _val;
    B *_b;

public:
    A(int val);
    void SetB(B *b);
    void Print();
};
#endif

// B_def.h
#ifndef B_DEF_H
#define B_DEF_H

class A;
class B
{
    double _val;
    A* _a;

public:
    B(double val);
    void SetA(A *a);
    void Print();
};
#endif

И тогда А.х и Б.ч будут содержать это:

// A.h
#ifndef A_H
#define A_H

#include "A_def.h"
#include "B_def.h"

inline A::A(int val) :_val(val)
{
}

inline void A::SetB(B *b)
{
    _b = b;
    _b->Print();
}

inline void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

#endif

// B.h
#ifndef B_H
#define B_H

#include "A_def.h"
#include "B_def.h"

inline B::B(double val) :_val(val)
{
}

inline void B::SetA(A *a)
{
    _a = a;
    _a->Print();
}

inline void B::Print()
{
    cout<<"Type:B val="<<_val<<endl;
}

#endif

Обратите внимание, что A_def.h и B_def.h являются "частными" заголовками, пользователи A и B не должны их использовать. Общедоступным заголовком является A.h и B.h.

...