Метод класса друга другого класса в двух отдельных файлах - PullRequest
0 голосов
/ 14 сентября 2018

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

A.HPP

#ifndef A_HPP
#define A_HPP

#include "B.hpp"
using namespace std;

class A
{
    protected:
        int a;
    public:
        A();
        ~A(){};
        friend void B::geta(A& ao);
};

inline A::A()
{
    a = 2;
    cout << a;
}

#endif

B.HPP

#ifndef B_HPP
#define B_HPP

using namespace std;

class A;

class B
{
    protected:
        int b;
    public:
        B();
        ~B(){};
        void geta(A& ao);
};

inline B::B()
{
    b = 1;
    cout << b;
}

inline void B::geta(A& ao)
{
    b = ao.a;
    cout << b;
}

#endif

main.cpp

#include <iostream>

#include "A.hpp"
#include "B.hpp"

int main()
{
    A ao;
    B bo;
    bo.geta(ao);
    return 0;
}

Когда я компилирую это, я получаю следующую ошибку.

error

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

Ответы [ 2 ]

0 голосов
/ 14 сентября 2018

Вариант 1: не встроен

Конечно, вы можете переместить определение B::geta в файл B.cpp, который включает в себя A.hpp, и удалить ключевое слово inline. Но это может сделать оптимизацию компилятора менее вероятной.

Вариант 2: Странная #include логика

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

A.hpp

// NO A_HPP include guard!
#ifdef INCLUDED_FROM_B_HPP

class A
{
    protected:
        int a;
    public:
        A();
        ~A(){};
        friend void B::geta(A& ao);
};

inline A::A()
{
    a = 2;
    cout << a;
}

#else
#  include "B.hpp"
#endif

B.hpp

#ifndef B_HPP
#define B_HPP

class A;

class B
{
    protected:
        int b;
    public:
        B();
        ~B(){};
        void geta(A& ao);
};

#define INCLUDED_FROM_B_HPP
#include "A.hpp"
#undef INCLUDED_FROM_B_HPP

inline B::B()
{
    b = 1;
    cout << b;
}

inline void B::geta(A& ao)
{
    b = ao.a;
    cout << b;
}

#endif

Так что теперь, если файл делает #include "B.hpp", препроцессор будет:

  1. Выведите class A; и определение B из первой части B.hpp.
  2. Определить INCLUDED_FROM_B_HPP.
  3. Вывести определения в A.hpp.
  4. Очистить определение INCLUDED_FROM_B_HPP.
  5. Вывести определения встроенных членов B из второй части B.hpp.

Если файл сначала #include "A.hpp", все немного сложнее:

  1. Так как INCLUDED_FROM_B_HPP не установлен, просто немедленно перейдите в B.hpp.
  2. Выведите class A; и определение B из первой части B.hpp.
  3. Определить INCLUDED_FROM_B_HPP.
  4. Когда встречается #include "A.hpp" в середине B.hpp, препроцессор рекурсивно возвращается в A.hpp. Но на этот раз, так как INCLUDED_FROM_B_HPP определен, он выводит содержимое кода A.hpp.
  5. Очистить определение INCLUDED_FROM_B_HPP.
  6. Вывести определения встроенных членов B из второй части B.hpp.

Вариант 3: friend class B;

Вместо указания только одной функции-члена B::geta другу, просто подружитесь с самим классом объявлением friend class B;. Теперь A.hpp не нужно включать B.hpp, поэтому проблема круговой зависимости отсутствует.

Это не сильно уменьшает инкапсуляцию с точки зрения того, что такое доступ и невозможен, поскольку обычно любой программист, который может изменить любую часть class B, может также модифицировать B::geta. Но это открывает возможности «случайного» использования непубличных членов A в других членах B.

Вариант 4: метод доступа к Refactor

A.hpp

#ifndef A_HPP
#define A_HPP

class B;

class A
{
    protected:
        int a;
    public:
        A();
        ~A(){};

        class AccessForB {
        private:
            static int geta(A& aobj) { return aobj.a; }
            friend class ::B;
        };
};

inline A::A()
{
    a = 2;
    cout << a;
}

#endif

B.hpp

...

inline void B::geta(A& ao)
{
    b = A::AccessForB::geta(ao);
    cout << b;
}

Этот код представляет новый вид инкапсуляции: теперь class B может получать значение только от члена a, не может изменять это значение и не может получить доступ к любым другим непубличным элементам A. Дополнительные средства доступа могут быть добавлены для других участников по мере необходимости. Чтобы разрешить изменение члена, класс может предоставить «набор» методов доступа или метод доступа, который возвращает ссылку. Для непубличных функций класс может предоставлять функции-оболочки, которые просто передаются действительной функции.

Все еще верно, что члены B, кроме B::geta, могут использовать дружбу, но теперь ввод A::AccessForB:: не может считаться случайностью.

0 голосов
/ 14 сентября 2018

... но они нужны мне в отдельных файлах.

Переместите встроенные функции в отдельный файл B.cpp, который #include A.hpp.

Будучи замеченным как перенаправленное объявление класса, объявление A еще не завершено в тот момент, когда вы начинаете его использовать в B.hpp, и компилятор справедливо жалуется на это.

Вы все еще можете сохранить ключевое слово inline, компилятор будет воспринимать его как обычную подсказку.

Вот эскиз:

B.hpp

#ifndef B_HPP
#define B_HPP

using namespace std;

class A;

class B
{
    protected:
        int b;
    public:
        B();
        ~B(){};
        void geta(A& ao);
};
#endif

B.cpp

#include "B.hpp"
#include "A.hpp"

inline B::B()
{
    b = 1;
    cout << b;
}

inline void B::geta(A& ao)
{
    b = ao.a; // <<<<<<<<<<<< here's the problem to be solved
    cout << b;
}

Эти вопросы и ответы очень связаны с вашей проблемой:

...