Функция друга и цикл включения

У меня проблема с функцией друга между двумя классами. Давайте посмотрим код:

Первый класс:

#ifndef _FIRST_H_
#define _FIRST_H_

//#include "Second.h"
#include <string>

class Second;
class First
{
    friend void Second::fun();

    std::string str = "Dziala\n";
public:
    First();
    ~First();
};
#endif

и второй класс:

#ifndef _SECOND_H_
#define _SECOND_H_

#include<iostream>
#include "First.h"

class Second
{
    First fObj;
public:
    Second();
    ~Second();
    void fun() { std::cout << fObj.str; }
};
#endif 

Нет проблем, если я попытаюсь подружиться с КЛАССОМ. Проблема возникает, если я сделаю друга FUNCTION, как в примере выше. Я могу исправить это с помощью #include "Second.h" в первом классе, но тогда это будет цикл включения. У вас есть идеи, как это сделать?

3 ответа

Проблема возникает, если я сделаю друга FUNCTION, как в примере выше.

// #include "Second.h"
#include <string>

class Second;
class First
{
    friend void Second::fun();
...

Первая строка - объявление класса Second, Это предварительная декларация. Для класса Secondпосле его объявления и до его определения, он является неполным типом. Так Second известен как тип класса, но члены, которые он содержит, неизвестны. Таким образом, вы не можете использовать член void Second::fun() Вот.

friend class Second работает нормально, так как никогда не пытается использовать элемент неполного типа.

но тогда это будет включать цикл.

Как сказал MadsMarquart, это не проблема, так как у вас уже есть защита заголовка.

есть идеи как это сделать?

Если вы хотите использовать friend void Second::fun() как объявление друга, порядок объявления и определения важен, и необходима небольшая модификация вашего класса.

  1. Объявить класс First,
  2. Определить класс Second с объявлением (не определением) fun(),
    • Вы не можете использовать First fObj как член или попытаться new First сейчас, так как First не определен и конструктор First сейчас неизвестно. Указатель или ссылка будут в порядке.
    • Поскольку указатель или ссылка используются, конструктор ** также должен быть изменен.
  3. Определить класс First с декларацией друга fun(),
  4. определять fun(),

Коды модифицированной базы на вашем примере,

class First;

class Second {
 public:
  Second(First& rfObj) : fObj(rfObj) {}
  void fun();

 private:
  First& fObj;
};

class First {
  friend void Second::fun();
 public:
  First() = default;

private:
  std::string str = "Dziala\n";
};

void Second::fun() { std::cout << fObj.str; }

Невозможно объявить функцию-член как друга, если не видно полное определение класса. Разрешение иначе разрешило бы произвольному коду объявлять и определять члены класса, которые не предназначен создателю этого класса.

class Second     // complete class definition, not just a forward declaration
{
    public:

       void fun();
};

class First
{
    friend void Second::fun();
};

Следствием этого является то, что First не может просто быть членом Second, Чтобы принять это, компилятор должен иметь видимость полного определения Second для определения First компилировать, но также иметь видимость полного определения First для определения Second Скомпилировать. Это бесконечно рекурсивная зависимость, которая имеет тенденцию расстраивать компиляторы.

Единственными типами членов класса (или вообще вообще переменных), которые могут быть объявлены только с помощью предварительного объявления, являются указатели или ссылки.

Итак, это будет работать

class First;

class Second     // complete class definition, not just a forward declaration
{
    private:
        First &fObj;    // note this is a reference
    public:

       void fun();

       Second();
       ~Second();
};

class First
{
    friend void Second::fun();
};

Second::Second() : fObj(*(new First))   // assumes First has appropriate (not shown) constructor
{}

Second::~Second()
{
   delete &fObj;
}

Однако обратите внимание, что как конструктор, так и деструктор Second также не может быть скомпилировано, если определение First виден компилятору заранее. Это связано с тем, что невозможно создать или уничтожить экземпляр типа класса только на основе предварительного объявления (т. Е. По той же причине, что и для исходной задачи).

Практически, я бы просто объявил класс Second быть другом First и покончим с этим. В конце концов, объявление одной функции-члена класса в качестве друга подтверждает, что функция-член всегда будет реализована так, чтобы работать так, как задумано. Существует очень немного обстоятельств, при которых можно доверять одной функции-члену класса для работы в соответствии с требованиями, но не доверять другим функциям-членам того же класса.

У вас есть идеи, как это сделать?

Вы не можете использовать friend механизм, чтобы делать то, что вы пытаетесь.

Простое решение - выставить First::str через функцию-член и не беспокоиться о friend построить. Это более чистое решение в долгосрочной перспективе.

class First
{
  public:
    First();
    ~First();

    std::string const& getString() const { return str; }

  private:
    std::string str = "Dziala\n";
};
Другие вопросы по тегам