Russian Qt Forum

Qt => Вопросы новичков => Тема начата: TsysarAndrew от Январь 19, 2014, 11:25



Название: Несколько вопросов от новичка в QT [Решено]
Отправлено: TsysarAndrew от Январь 19, 2014, 11:25
Добрый день!

Пытаюсь освоить QT (5.2.0) под windows, появилось несколько вопросов:
1) Есть проект, в нем класс Data, пытаюсь сделать для него юнит-тесты. Создал при помощи мастера проект типа "QT Unit Test", в файле .pro прописал при помощи INCLUDEPATH путь к исходникам тестируемого проекта, в .cpp теста при помощи #include прописал хедер класса Data (хедер находится по Ctr+пробел и открывается по Ctr+клик левой кнопкой), в классе теста объявлено поле
Код:
Data *f_data;
, далее в конструкторе прописано:
Код:
f_data = new Data();

и в одном из тестовых методов
Код:
f_data->proccessFile(...);

Получаю ошибки компиляции:
а) Для кода в конструторе: undefined reference to `Data::Data()'
б) Для кода в методе теста: undefined reference to `Data::proccessFile(QString, QString)'

Если закомментировать указанный код и оставить только объявление поля f_data, то проект компилируется, а при остановке отладчика в одном из тестовых методов видно, что данное поле проинициализировано. Не могу понять в чем тут проблема...

2) Объясните как правильно инициализировать поля классов. Например, объявляю я поля в классе с типом класс. Если в конструкторе специально не прописывать инициализацию (программирую в Delphi, там это обязательно), то объект все равно создается,  но в какие-то моменты жизни программы он доступен, а другие нет. Например, есть класс с полем типа QVector, он (класс) предоставляет метод для регистрации у себя других объектов, создается глобальный экземпляр этого класса-регистратора, далее регистрируемые классы у себя в конструкторе пытаются зарегистрироваться, при этом для одних регистрация проходит нормально, для других получается, что при вызове метода регистрации поле QVector еще не инициализировано...

3) Зачем в хедерах объявления классов оборачиваются в конструкции вида:
Код:
#ifndef DATA_H
#define DATA_H

....

#endif // DATA_H


Название: Re: Несколько вопросов от новичка в QT
Отправлено: Vamireh от Январь 19, 2014, 12:31
1) Конструктор по умолчанию есть?
2) Не понял вопроса. Я инициализирую так:
Data::Data() :
p_data(),
p_some(),
...
{
}
3) Не знаю, как сформулировать. Короче, это нужно, если этот хедер подключается из нескольких файлов. Чтобы не было множественного определения. А так дефайнится DATA_H если он не задефайнен, а при последующих подключениях - он уже задефайнен и дальнейший код не выполняется. Хотя можно просто писать вверху #pragma once, но по теории это не всеми компиляторами поддерживается (а если не поддерживается - то не ругнется даже), но на практике я не сталкивался, чтобы это не работало.


Название: Re: Несколько вопросов от новичка в QT
Отправлено: Bepec от Январь 19, 2014, 12:48
2) Это называется страж. Позволяет подключать класс только один раз. Если подключить в множестве мест, вылетят интересные ошибки и у компилятора голова заболит. Каждый страж должен быть уникальным. Многие используют GUID вместо "НАЗВАНИЕ_КЛАССА".


Название: Re: Несколько вопросов от новичка в QT
Отправлено: Igors от Январь 19, 2014, 15:24
Например, есть класс с полем типа QVector, он (класс) предоставляет метод для регистрации у себя других объектов, создается глобальный экземпляр этого класса-регистратора, далее регистрируемые классы у себя в конструкторе пытаются зарегистрироваться, при этом для одних регистрация проходит нормально, для других получается, что при вызове метода регистрации поле QVector еще не инициализировано...
Неясно что подразумевается под "регистрацией". Приведите пример кода - тогда можно ответить конкретно


Название: Re: Несколько вопросов от новичка в QT
Отправлено: TsysarAndrew от Январь 19, 2014, 18:42
1) Конструктор по умолчанию есть?
Не совсем понял что это такое.. Если под конструктором по умолчанию подразумевается конструктор без параметров, то да, такой конструктор есть. У прощенный код объявления класса:
Код:
class Data
{
public:
    Data();
    ~Data();
....
};

Дело в том, что в самом рабочем проекте для создания объекта этого типа используется тот же самый код:
Код:
f_data = new Data();
и там все работает..


Название: Re: Несколько вопросов от новичка в QT
Отправлено: TsysarAndrew от Январь 19, 2014, 19:35
Неясно что подразумевается под "регистрацией". Приведите пример кода - тогда можно ответить конкретно

Пример: есть класс ObjectsDispatcher:
Код:
class ObjectsDispatcher
{
public:
    ObjectsDispatcher();
    void registerObject(IObject* object);
    .....
private:
   QMap<QString, IObject*> f_objects;
};

Также есть некоторое количество наследников класса IObject, у которых в конструкторе прописано
Код:
qObjectsDispatcher.registerObject(this);

qObjectsDispatcher объявлен в objectsdispatcher.h:
Код:
.....
#endif // OBJECTSDISPATCHER_H
extern ObjectsDispatcher qObjectsDispatcher;

объект создается в objectsdispatcher.cpp:
Код:
 ObjectsDispatcher qObjectsDispatcher;

Далее получается, что при создании некоторых наследников IObject все проходит хорошо, а при создании других (вроде это происходило при добавлении в проект новых наследников, для каждого такого класса аналогичным образом создавался глобальный объект, которые и регистрировал себя в диспетчере) происходит исключение SIGSEGV, при этом в отладчике поле f_objects класса ObjectsDispatcher отображается как <not accessible>. Немного позже конструктор класса сделал таким (до этого его тело было пустым):
Код:
ObjectsDispatcher::ObjectsDispatcher()
{
    f_objects.clear();
}

теперь вроде все работает, но не понятно почему так работает. Особенно не понятно когда создается f_objects, почему не нужно явно его инициализировать. Ранее описанная проблема с QVector примерно такая же (там тоже вызвал clear по аналогии и вроде все пока работает).


Название: Re: Несколько вопросов от новичка в QT
Отправлено: _OLEGator_ от Январь 19, 2014, 20:04
объект создается в objectsdispatcher.cpp:
Код:
 ObjectsDispatcher qObjectsDispatcher;

Все, дальше можно и не читать и на этой строчке отправлять учить C++, а уже после этого переходить к Qt.


Название: Re: Несколько вопросов от новичка в QT
Отправлено: Igors от Январь 19, 2014, 20:09
Сначала поставьте точку останова в ObjectsDispatcher::ObjectsDispatcher и вторую в ObjectsDispatcher::registerObject. Убедитесь что конструктор срабатывает первым (хоть и редко но возможно что нет). В любом случае  f_objects.clear() - просто заплатка которая ничего не решает, вылезет в др месте. Добавьте метод печатающий ObjectsDispatcher::f_objects и натыкайте его вызовы (ищите где портится)

Основная идея: если класс имеет конструктор, то невозможно создать объект такого класса минуя вызов конструктора. Явное указание конструктора выбирает который используется, но все равно 1 (и только 1) конструктор выполнится. То же самое для всех членов класса.

Все, дальше можно и не читать и на этой строчке отправлять учить C++, а уже после этого переходить к Qt.
Не понял столь жесткой критики - ну объявил человек глобальную переменную без всяких затей/синглтонов, что в этом такого уж плохого?


Название: Re: Несколько вопросов от новичка в QT
Отправлено: TsysarAndrew от Январь 19, 2014, 20:11
Все, дальше можно и не читать и на этой строчке отправлять учить C++, а уже после этого переходить к Qt.

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


Название: Re: Несколько вопросов от новичка в QT
Отправлено: TsysarAndrew от Январь 19, 2014, 22:31
Добавьте метод печатающий ObjectsDispatcher::f_objects и натыкайте его вызовы (ищите где портится)
Я примерно так и делал, и в итоге дошел до такого костыля. Когда в следующий раз вылезет, попробую разобраться более основательно. Сейчас меня беспокоит отставание в проекте тестирования от основного проекта. Жаль, что про инициализацию объектов так никто и не ответил. Может быть это совсем тривиальный вопрос, но он меня постоянно отвлекает, и возможно я не там ищу проблемы.


Название: Re: Несколько вопросов от новичка в QT
Отправлено: Igors от Январь 20, 2014, 10:13
Жаль, что про инициализацию объектов так никто и не ответил. Может быть это совсем тривиальный вопрос, но он меня постоянно отвлекает, и возможно я не там ищу проблемы.
Как минимум один (я) ответил  :) Конструктор класса выполняется всегда - это и есть ответ на Ваш вопрос


Название: Re: Несколько вопросов от новичка в QT
Отправлено: TsysarAndrew от Январь 20, 2014, 22:00
С первой проблемой вроде разобрался: я прописал путь к файлам проекта, но не включил сами файлы тестируемого проекта в проект тестирования..
По второй проблеме пока наблюдаю как раз картину создания объекта без конструктора (может вызывается какой-то другой конструктор, который я не перекрыл/объявил?). Все глобальные объекты (ObjectsDispatcher и наследники IObject) содаются как было описано ранее. Получается, что наследник IObject может создаваться раньше ObjectsDispatcher-а, и тогда при вызове метода qObjectsDispatcher.registerObject сам метод вызывается (т.е. объект qObjectsDispatcher создан), но его поле f_objects непроинициализировано, выходит ошибка. С этим пока не разобрался..



Название: Re: Несколько вопросов от новичка в QT
Отправлено: Serr500 от Январь 21, 2014, 04:13
По второй проблеме пока наблюдаю как раз картину создания объекта без конструктора (может вызывается какой-то другой конструктор, который я не перекрыл/объявил?).
Объект, у которого есть конструктор, не может быть создан без его вызова. На самом деле, конструктор есть у любого объекта, для которого выделяется память. Конструкторы различаются только по спискам параметров, проверьте все ли создания объектов нужного типа вызывают переопределённые конструкторы.

Получается, что наследник IObject может создаваться раньше ObjectsDispatcher-а
При таком объявлении ObjectsDispatcher'а может. Чтобы избежать такой ситуации, не используйте глобальные объекты, их порядок создания отдаётся на откуп компилятору. Если же без глобальных объектов обойтись нельзя, используйте синглтоны с автоматическим созданием объекта при первом обращении.


Название: Re: Несколько вопросов от новичка в QT
Отправлено: Igors от Январь 21, 2014, 11:04
По второй проблеме пока наблюдаю как раз картину создания объекта без конструктора (может вызывается какой-то другой конструктор, который я не перекрыл/объявил?). Все глобальные объекты (ObjectsDispatcher и наследники IObject) содаются как было описано ранее. Получается, что наследник IObject может создаваться раньше ObjectsDispatcher-а, и тогда при вызове метода qObjectsDispatcher.registerObject сам метод вызывается (т.е. объект qObjectsDispatcher создан), но его поле f_objects непроинициализировано, выходит ошибка. С этим пока не разобрался..
Если глобальные переменные находятся в разных единицах трансляции (cpp файлах), то порядок их инициализации в общем случае не определен.  Можно по-простому собрать их в один cpp файл, тогда они создаются в порядке объявления. Или городить пресловутый синглтон, напр так
Код
C++ (Qt)
class ObjectDispatcher {
public:
 ...
 static ObjectDispatcher & Instance()
 {
   static ObjectDispatcher dispatcher;
   return dispatcher;
 }
};
И использовать ObjectDispatcher::Instance() (переменную qbjectDispatcher убить) 


Название: Re: Несколько вопросов от новичка в QT
Отправлено: TsysarAndrew от Январь 21, 2014, 16:29
Ситуация следующая: есть диспетчер, который содержит информацию о правилах обработки данных. Для каждого правила выделяется отдельный класс. Правил скорее всего будет несколько десятков. В этой ситуации хотелось бы добиться максимальной независимости модулей. Для большинства правил нет необходимости создавать несколько экземпляров.
Было решено сделать это следующим образом: создать почти абстрактный класс-предок для всех правил (IObject), в котором будет реализован только метод регистрации правила в глобальном диспетчере. Все правила-потомки в своих конструкторах вызывают этот метод и так регистрируются в диспетчере. При такой схеме получается только одна межмодульная связь между хедерами iobject.h и objectdispatcher.h, а все все остальные объекты ничего друг о друге не знают (кроме связи наследник-родитель, но от нее никак не избавиться).
Немного поразмыслив над смыслом препроцессорных стражей включения хедеров пришел к выводу, что если в iobject.h будет включен objectdispatcher.h, то в результате перед компиляцией глобальная переменная диспетчера по тексту должна быть выше глобальных переменных правил. Просмотрев исходники вспомнил, что так и хотел сделать с самого начала, но тогда всплыла проблема cross including-а модулей, и я хедер objectdispatcher.h включил не в iobject.h, а в iobject.cpp. Не знаю почему так произошло, т.к. проблема решается просто. Разобрался с cross including-ом, сейчас вроде все работает как нужно.
По результатам этой работы возникает вопрос о возможных подводных камнях такого решения. Также хотелось бы до конца разобраться почему система сразу не выдала исключение о не существовании объекта qObjectsDispatcher, а давала возможность вызвать один из его методов. Если не пропадет желание, то соберу небольшой пример с демонстрацией проблемы и выложу сюда для обсуждения.


Название: Re: Несколько вопросов от новичка в QT
Отправлено: Serr500 от Январь 21, 2014, 17:28

Немного поразмыслив над смыслом препроцессорных стражей включения хедеров пришел к выводу, что если в iobject.h будет включен objectdispatcher.h, то в результате перед компиляцией глобальная переменная диспетчера по тексту должна быть выше глобальных переменных правил.
Порядок инициализации зависит от порядка в файлах реализации (.c|.cpp), а не в файлах включения (.h|.hxx|.hpp)!

Также хотелось бы до конца разобраться почему система сразу не выдала исключение о не существовании объекта qObjectsDispatcher, а давала возможность вызвать один из его методов.
Потому что он в некотором роде существовал.  ;) Просто не был до конца инициализирован.


Название: Re: Несколько вопросов от новичка в QT
Отправлено: Igors от Январь 21, 2014, 18:04
Просмотрев исходники вспомнил, что так и хотел сделать с самого начала, но тогда всплыла проблема cross including-а модулей, и я хедер objectdispatcher.h включил не в iobject.h, а в iobject.cpp. Не знаю почему так произошло, т.к. проблема решается просто. Разобрался с cross including-ом, сейчас вроде все работает как нужно.
По результатам этой работы возникает вопрос о возможных подводных камнях такого решения.
Такое решение как минимум "ненадежно". Напр файл (случайно) был удален из проекта но потом сразу добавлен назад. Или порядок cpp файлов по каким-то др причинам изменился. Или просто др компилятор или даже новая версия - и все, порядок линковки изменился, приплыли.

Простые решения были предложены выше, велосипедить здесь не стоит. Не хотите синглтон (кстати модный, см вику) - соберите все конфликтующие глобалы в одном cpp файле (предназначенном только для этого). Это кстати вполне по-паскалевски. А классы ObjectDispatcher и IObject по-любому друг друга видят


Название: Re: Несколько вопросов от новичка в QT
Отправлено: TsysarAndrew от Январь 21, 2014, 18:37
Потому что он в некотором роде существовал.  ;) Просто не был до конца инициализирован.
Про это можно что-нибудь почитать? Раньше  в других ЯП с такими ситуациями не сталкивался.


Название: Re: Несколько вопросов от новичка в QT
Отправлено: TsysarAndrew от Январь 21, 2014, 18:46
Проверил, что порядок .cpp  в файле проекта влияет на обсуждаемую ситуацию. Наверно пока сделаю один модуль для всех глобальных объектов. Потом может посмотрю в сторону паттернов.


Название: Re: Несколько вопросов от новичка в QT
Отправлено: Serr500 от Январь 21, 2014, 19:00
Потому что он в некотором роде существовал.  ;) Просто не был до конца инициализирован.
Про это можно что-нибудь почитать? Раньше  в других ЯП с такими ситуациями не сталкивался.
Мнэ-э-э-э... Х.з.  :-\ Не помню, где я такое вычитал... Может, у Страуструпа...

Ну, здесь, если мне память не изменяет, логика следующая. Глобальная переменная размещается в стеке, т.е. при запуске программы ей выделяется стековая память. Но выделяется она не так, как хотелось бы - просто резервируется кусок стека для всех глобальных переменных. Точно известно где расположена наша переменная, т.е. её смещение от вершины стека, поэтому мы можем к ней обратиться. Однако, конструктор для переменной ещё не вызван и она находится в неопределённом состоянии. Конструктор будет вызван где-то далее, но до запуска main(). Если другая глобальная переменная пытается при создании обратиться к первой, то мы и получаем ошибку.
Это примерно то же самое, что и обращение к методу/члену класса по нулевому указателю. Иногда такое прокатывает, но чаще всего вызывает ошибку. Упрощённо можно считать, что после выделения памяти до вызова конструктора переменная как бы имеет нулевой (или, если удобнее, "неправильный") указатель.

P.S. Где-то видел вот такую пакость:
Код:
((SomeClass*)NULL)->member();


Название: Re: Несколько вопросов от новичка в QT
Отправлено: TsysarAndrew от Январь 21, 2014, 19:46
Спасибо всем откликнувшимся. Прояснил для себя некоторые вопросы про c++. Тема закрыта.


Название: Re: Несколько вопросов от новичка в QT
Отправлено: Igors от Январь 21, 2014, 20:17
Ну, здесь, если мне память не изменяет, логика следующая. Глобальная переменная размещается в стеке, т.е. при запуске программы ей выделяется стековая память. Но выделяется она не так, как хотелось бы - просто резервируется кусок стека для всех глобальных переменных. Точно известно где расположена наша переменная, т.е. её смещение от вершины стека, поэтому мы можем к ней обратиться.
Правильно, но только никак не в стеке  :) Вообще для глобальных переменных процесс "выделения памяти" отсутствует - один кусок на всех который выделяется ОС'ом при старте приложения. Обращение производится по смещению от начала этого куска, как Вы сказали.

Когда-то по этому поводу почитывал стандарт (небольшое удовольствие, поэтому борзые знатоки любят к нему отсылать :)). Там вообще нет термина "глобальная", а есть "non-local variable(s)". Т.е. есть локальные (четко на стеке) - и все остальные. Константы, статики - все это non-local.

Компилятор генерирует ф-ции инициализации для всех глобалов и она выполняется до main. Аналогично ф-ция разрушения после выхода из main. Легко убедиться что идеального порядка инициализации быть не может, напр
Код
C++ (Qt)
class A {
..
globalB.registerA(this);
};
A globalA;
 
class B {
..
globalA.registerB(this);
};
B globalB;
 


Название: Re: Несколько вопросов от новичка в QT [Решено]
Отправлено: Serr500 от Январь 21, 2014, 21:01
Правильно, но только никак не в стеке  :) Вообще для глобальных переменных процесс "выделения памяти" отсутствует - один кусок на всех который выделяется ОС'ом при старте приложения.
Стар уже стал, всё позабыл...  :-\ Действительно, это не STACK, а DATASEG - сегмент данных приложения.