Как использовать Q_LOGGING_CATEGORY и ссылаться на категорию в шаблонной функции без получения множественной ошибки реализации?

#c #qt #templates

#c #qt #шаблоны

Вопрос:

Я столкнулся с проблемой с одним из моих заголовочных файлов в проекте QT.

Обычно я использую следующую конструкцию:

global.h

 #ifndef global
#define global
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(cat)
#endif
  

foo.h

 #ifndef foo
#define foo
#include "global.h"
class foo{
 public:
   void bar();
};
#endif
  

foo.cpp

 #include "foo.h"
Q_LOGGING_CATEGORY(cat, "awesomecategory")
void foo::bar(){
qCDebug(cat) << "helloworld";
}
  

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

foo.h

 #ifndef foo
#define foo

#include "global.h"

template <typename T>
bool bar(T, int val);

// Template definitions outside of header:
#include "foo.tpp"

#endif // foo
  

foo.tpp

 #include <QLoggingCategory>
Q_LOGGING_CATEGORY(cat, "awesomecategory")
template <typename T>
bool bar(T, int val)
{
        T baz;
        // do stuff with baz
        qCDebug(cat) << "helloworld";
}
  

Что работает, если я вызываю шаблонную функцию в одном другом cpp-файле (куда я поэтому, конечно, включаю foo.h ), но как только я включаю foo.h в другой cpp-файл, где я хочу вызвать функцию, я получаю ошибку множественного определения, подобную этой:

 In function `ZN11QStringListD1Ev':
C:pathtofoo.h:{n}: multiple definition of `cat()'
C:pathtofoo.h:{n}: first defined here
collect2.exe: error: ld returned 1 exit status
  

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

Ответ №1:

Вы можете сделать это только Q_LOGGING_CATEGORY(cat, "awesomecategory") один раз, потому что это, по сути, создает «глобальную» функцию, откуда бы она ни вызывалась (см. Ниже). Также, когда вы #include "foo.tpp" просто помещаете содержимое этого файла в заголовок (это не «отдельный блок», как, например, исходный файл .cpp).

Если вам нужна глобальная категория ведения журнала с именем cat , вы могли бы просто создать это прямо в вашем global.h . Я не думаю, что Q_LOGGING_CATEGORY(cat, "awesomecategory") это будет работать непосредственно в заголовке, но это легко обойти.

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

 #define Q_DECLARE_LOGGING_CATEGORY(name) 
    extern const QLoggingCategory amp;name();

#define Q_LOGGING_CATEGORY(name, ...) 
    const QLoggingCategory amp;name() 
    { 
        static const QLoggingCategory category(__VA_ARGS__); 
        return category; 
    }
  

Таким образом, DECLARE на самом деле это просто прямое объявление экспортируемой функции. С таким же успехом вы могли бы ввести это global.h вместо этого (и полностью пропустить все Q макросы):

 static const QLoggingCategory amp;cat()
{
    static const QLoggingCategory category("awesomecategory");
    return category;
}
  

Оно должно быть глобальным или, по крайней мере, в том же пространстве имен по умолчанию, или объявлено только в одном блоке, потому что…

 #define qCDebug(category, ...) 
    for (bool qt_category_enabled = category().isDebugEnabled(); qt_category_enabled; qt_category_enabled = false) 
        QMessageLogger(QT_MESSAGELOG_FILE, QT_MESSAGELOG_LINE, QT_MESSAGELOG_FUNC, category().categoryName()).debug(__VA_ARGS__)
  

Или, другими словами, cat() функция, созданная ранее, вызывается в единичном (или, в нашем примере, глобальном) контексте.

Конечно, если вызывать процедуры ведения журнала в стиле категории только из членов одного класса, то cat() метод может просто принадлежать этому классу.

Или cat() сама функция может находиться в пространстве имен или даже в своем собственном классе…

 class MyLoggers
{
  public:
    static const QLoggingCategory amp;cat()
    {
        static const QLoggingCategory category("awesomecategory");
        return category;
    }
}

// used somewhere else:
qCDebug(MyLoggers::cat)  << "Hello cats!";
  

Это намного более гибко, чем могут показаться эти простые макросы.

HTH

ДОБАВЛЕНО: Приведенный ниже конкретный пример, похоже, работает без ошибок.

global.h

 #ifndef GLOBAL_H
#define GLOBAL_H

#include <QLoggingCategory>

static const QLoggingCategory amp;cat()
{
    static const QLoggingCategory category("awesomecategory");
    return category;
}

#endif // GLOBAL_H
  

foo.h

 #ifndef FOO_H
#define FOO_H

#include "global.h"

template <typename T>
bool bar(T, int)
{
    qCDebug(cat) << "helloworld";
    return true;
}

#endif // FOO_H
  

baz.h

 #ifndef BAZ_H
#define BAZ_H

#include "global.h"

inline void baz()
{
    qCDebug(cat) << "baz() says meow.";
}

#endif // BAZ_H
  

main.cpp

 #include "foo.h"
#include "baz.h"

int main(int argc, char *argv[])
{
    bar<int>(2, 2);
    baz();
    return 0;
}
  

С принтами:

 awesomecategory: helloworld
awesomecategory: baz() says meow.
  

Комментарии:

1. Спасибо за подробное объяснение. Но даже если я включу вышеупомянутую конструкцию в global.h , как только я использую свою функцию, которая вызывает статическую функцию в global.h в разных блоках, я все равно получаю много ошибок. Итак, мой текущий обходной путь — поместить имя plaintext в вызов: qCWarning(QLoggingCategory("name") << "blahblah....";

2. @darkmattercoder Я добавил конкретный пример к своему ответу, который работает в моем тестовом проекте. Если это не поможет, возможно, вы могли бы подробнее рассказать о том, какие ошибки вы получаете и как выглядит ваш тестовый код сейчас (возможно, в качестве правки к вашему исходному вопросу, если комментарий слишком длинный).

3. Что ж, спасибо за добавление примера. Должно быть, у меня было что-то другое, когда я пытался в прошлый раз. Я сделал это снова с помощью вашего ввода, и это сработало!