#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. Что ж, спасибо за добавление примера. Должно быть, у меня было что-то другое, когда я пытался в прошлый раз. Я сделал это снова с помощью вашего ввода, и это сработало!