Как я могу обрабатывать циклические вызовы #include в заголовке класса шаблона? - PullRequest
0 голосов
/ 18 апреля 2019

Относится к Ошибка "Неопределенная условная директива" в заголовках перекрестных ссылок

У меня есть класс шаблона Serializable:

serializable.h

#pragma once
#ifndef SERIALIZABLE_H
#define SERIALIZABLE_H
#include "Logger.h"
#include <string>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/exception_ptr.hpp>

template<class T>
class Serializable {
public:
    static bool Deserialize(Serializable<T>* object, std::string serializedObject) {
        try {
            return object->SetValuesFromPropertyTree(GetPropertyTreeFromJsonString(serialized));
        } catch (...) {
            std::string message = boost::current_exception_diagnostic_information();
            Logger::PostLogMessageSimple(LogMessage::ERROR, message);
            std::cerr << message << std::endl;
        }
    }
private:
    static boost::property_tree::ptree GetPropertyTreeFromJsonString(const std::string & jsonStr) {
        std::istringstream iss(jsonStr);
        boost::property_tree::ptree pt;
        boost::property_tree::read_json(iss, pt);
        return pt;
    }
}
#endif // SERIALIZABLE_H

Но проблема в том, что класс Logger использует объект LogMessage, который наследуется от Serializable (с использованием CRTP).

Logger.h

#pragma once
#ifndef LOGGER_H
#define LOGGER_H
#include "LogMessage.h"

class Logger {
public:
    static void PostLogMessageSimple(LogMessage::Severity severity, const std::string & message);
}
#endif // LOGGER_H

LogMessage.h

#pragma once
#ifndef LOGMESSAGE_H
#define LOGMESSAGE_H
#include "serializable.h"

class LogMessage : public Serializable<LogMessage> {
public:
    enum Severity {
        DEBUG,
        INFO,
        WARN,
        ERROR
    };
private:
    std::string m_timestamp;
    std::string m_message;

    friend class Serializable<LogMessage>;
    virtual boost::property_tree::ptree GetNewPropertyTree() const;
    virtual bool SetValuesFromPropertyTree(const boost::property_tree::ptree & pt);
}

#endif // LOGMESSAGE_H

Проблема заключается в том, что каждый из этих файлов содержит другой, который вызывает ошибки сборки.К сожалению, я не могу использовать решение из вышеприведенного вопроса (переместить #include "Logger.h" в Serializable.cpp), потому что Serializable является классом шаблона и поэтому должен быть определен в заголовочном файле.

Я не знаю, как поступить.Любая помощь приветствуется!

Редактировать: я также рассмотрел использование предварительных объявлений Logger и LogMessage внутри serializable.h, но так как я вызываю статические методы в Logger и использую LogMessage :: Severity, это неработа.

Ответы [ 2 ]

3 голосов
/ 20 апреля 2019

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

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


На одном уровне вы можете посмотреть на намерения классов.Забудьте код и сосредоточьтесь на цели.Имеет ли смысл для класса A быть неспособным определить себя, не зная, что такое класс B?Имейте в виду, что это сильнее, чем знать, что класс B существует (что будет соответствовать предварительному определению, заголовок не требуется).Если это не имеет смысла, не глядя на код, то, возможно, вы нашли что-то, над чем можно поработать.Следует признать, что использование шаблонов усложняет ситуацию, поскольку вся реализация должна быть в заголовке.

Например, Serializable действительно должен иметь возможность определять себя, не зная, что будет сделано с сериализацией (т. Е.Logger).Однако это шаблон, и его реализация должна иметь возможность регистрировать ошибки.Итак ... хитро.

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

Serializable -> LoggerBase
Logger -> LoggerBase
Logger -> LogMessage -> Serializable -> LoggerBase

На другом уровне вы можете детально взглянуть на код, не слишком заботясь о цели.У вас есть заголовок A, включая заголовок B - почему?Какие части A на самом деле используют что-то из B?Какие части B на самом деле используются?Составьте диаграмму, если вам нужно лучше представить, где находится эта зависимость.Затем внесите некоторое внимание в цель.Определены ли эти части в соответствующих местах?Существуют ли другие возможности?

Например, в примере кода причина, по которой Serializable нужно LogMessage, заключается в получении доступа к перечислению LogMessage::ERROR.Это не является серьезной причиной необходимости полного определения LogMessage.Возможно, оболочка типа PostLogErrorSimple могла бы устранить необходимость знать константу серьезности?Может быть, реальность сложнее, но дело в том, что некоторые зависимости можно обойти, поместив зависимость в исходный файл.Иногда исходный файл предназначен для другого класса.

Другой пример взят из класса Logger.Для этого класса требуется LogMessage, чтобы получить доступ к перечислению LogMessage::Severity (т. Е. Перечислению, в котором ERROR имеет одно из своих значений).Это также не является серьезной причиной необходимости полного определения класса.Возможно перечисление должно быть определено в другом месте?Как часть Logger возможно?Или, может быть, не в определении класса вообще?Если этот подход работает, круг зависимостей разбивается на цепочки:

Serializable -> Severity
Serializable -> Logger -> Severity // To get the PostLogMessageSimple function
Logger -> Severity

В идеале, после того, как перечисление позаботится, заголовок Logger сможет обойтись только с помощью предварительного объявления LogMessage вместо включения полного заголовка.(Предварительного объявления достаточно, чтобы получить объект по ссылке. И, вероятно, полное определение Logger будет иметь некоторые функции, принимающие LogMessage параметры.)

0 голосов
/ 20 апреля 2019

Если у вас просто есть объявления в вашем классе, и вы определяете методы вне строки, вы должны иметь возможность заставить его работать:

#pragma once
#ifndef SERIALIZABLE_H
#define SERIALIZABLE_H
#include <string>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/exception_ptr.hpp>

template<class T>
class Serializable {
public:
    static bool Deserialize(Serializable<T>* object, std::string serializedObject);
private:
    static boost::property_tree::ptree GetPropertyTreeFromJsonString(const std::string & jsonStr);
}

#include "Logger.h"

template < typename T >
inline bool Serializable<T>::Deserialize(Serializable<T>* object, std::string serializedObject) {
        try {
            return object->SetValuesFromPropertyTree(GetPropertyTreeFromJsonString(serialized));
        } catch (...) {
            std::string message = boost::current_exception_diagnostic_information();
            Logger::PostLogMessageSimple(LogMessage::ERROR, message);
            std::cerr << message << std::endl;
        }
    }

template < typename T >
inline boost::property_tree::ptree Serializable<T>::GetPropertyTreeFromJsonString(const std::string & jsonStr) {
        std::istringstream iss(jsonStr);
        boost::property_tree::ptree pt;
        boost::property_tree::read_json(iss, pt);
        return pt;
    }

#endif // SERIALIZABLE_H

Это дает дополнительный бонус, делающий интерфейс вашего класса более понятным.

...