Я знаю, что существует миллион тем, объясняющих, как использовать предварительную декларацию и включать охранников. Я приехал из Явы, где мне не пришлось беспокоиться об этих вещах. Это моя первая программа на С ++, так что со мной (и давать советы).
Вот моя проблема …
у меня есть родительский класс под названием Token
я имею два детских класса, ErrorToken и EndToken. Каждый из этих классов должен иметь возможность создавать объект другого, и вернуть его через вызов функции. Каждый из них имеет свой отдельный класс заголовков.
Таким образом, ErrorToken должен иметь возможность создавать новый объект EndToken и возвращать его, а EndToken должен иметь возможность создавать новый объект ErrorToken и возвращать его.
Что будет лучшим способом добиться успеха в этом? Я бы предпочел, чтобы он был как можно более совместимым с кросс-компилятором, Я не хочу использовать прагму один раз. (но это по сути то, что я ищу).
В идеале я хотел бы иметь возможность сделать что-то вроде этого …
#ifndef ErrorToken
#include "ErrorToken.h"#endif
Но это никогда не работает (и я думаю, что это неправильно? Может кто-нибудь помочь мне понять, почему?).
Я понимаю, что предварительное объявление работает только с сигнатурами функций и указателями (это правильно?), Поэтому я не думаю, что это сработает в моей ситуации, поскольку мне нужно, чтобы он мог запускать конструктор … или компилятору просто нужно знать, что конструктор завершается в этот момент?
Ну, используйте предварительные декларации. Как вы сказали, существуют миллионы объяснений, а теперь миллионы и одно:
ErrorToken.h:
#ifndef H_ERROR_TOKEN
#define H_ERROR_TOKEN
#include "Token.h"
class EndToken;
class ErrorToken : public Token
{
public:
EndToken makeEndToken();
};
#endif
EndToken.h:
#ifndef H_END_TOKEN
#define H_END_TOKEN
#include "Token.h"
class ErrorToken;
class EndToken : public Token
{
public:
ErrorToken makeErrorToken();
};
#endif
Теперь в каждом файле реализации вы можете включить оба заголовка:
#include "ErrorToken.h"#include "EndToken.h"
ErrorToken EndToken::makeErrorToken()
{
return ErrorToken(); // example
}
EndToken ErrorToken::makeEndToken()
{
return EndToken();
}
Как отметил @James Kanze, вы можете быть не уверены, как работает C ++. Следующий код может более соответствовать типу поведения, которое вы ожидаете от Java, и имеет больше смысла в полиморфном способе проектирования:
class Token { virtual ~Token() {} };
class ErrorToken : public Token
{
std::unique_ptr<Token> makeEndToken();
};
class EndToken : public Token
{
std::unique_ptr<Token> makeErrorToken();
};
std::unique_ptr<Token> EndToken::makeErrorToken()
{
return { new ErrorToken; }
}
std::unique_ptr<Token> ErrorToken::makeEndToken()
{
return { new EndToken; }
}
Поскольку вы обрабатываете объекты только через базовые указатели, заголовки не должны знать что-нибудь о других производных классах. (Я оставляю вам возможность подразделять код на файлы; каждый блок помещается в отдельный файл.)
Почему твой
#ifndef ErrorToken
неправильно? Во-первых, поместите его в заголовочный файл, во-вторых, убедитесь, что что-то на самом деле определяет ErrorToken
Включите охранников и предварительные декларации, которые могут вывести вас из этой дыры:
//"ErrorToken.h"#ifndef ERROR_TOKEN_INCLUDED
#define ERROR_TOKEN_INCLUDED
class EndToken;
class ErrorToken
{
public:
EndToken DoSomething();
};
#endif
//"EndToken.h"#ifndef END_TOKEN_INCLUDED
#define END_TOKEN_INCLUDED
class ErrorToken;
class EndToken
{
public:
ErrorToken DoSomething();
};
#endif
Было несколько предыдущих обсуждений круговых зависимостей: Вот, Вот а также Вот
Затем вы можете выполнить большую часть работы в файле cpp, где вы включаете другой заголовок, чтобы он знал полное определение класса, а не только предварительное объявление, и мог использовать его.
Сделайте первый шаг назад и спросите: «Они действительно должны знать друг о друге?»