Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
We are planning to upgrade qdoc to use clang for parsing C++
connect(sender, &Sender::valueChanged, receiver, &Receiver::updateValue );class IntegralControllerBase : public QObject
{
Q_OBJECT
// Здесь всё, что не нужно выносить в шаблон, в т.ч. подключение слота к сигналу.
protected slot:
virtual void valueChanged( int ) = 0;
};
template<typename T_IntegralType> class IntegralController : public IntegralControllerBase
{
private:
T_IntegralType *_modelField;
private:
void valueChanged( int inValue ) { _modelField = inValue; }
};В общем, с учётом того, что от MOC-системы мне была нужна лишь возможность подписываться на события через статический метод QObject::connect(...), я решил написать небольшой костыль, то тогда я не вижу смысла городить все эти ужасы с макросами, классами и т.п. Весь Qt позволяет подписываться на события и просто функциями. А учётом того, что этой функций может быть и лямбда, мы легко получаем механизм подписки на события любым своим (не входящий в метасистему Qt и соответственно хоть с шаблонами, хоть с чем угодно) классом в виде конструкции connect(sender, signal, [=]{reciver_object.handler();}).
хотя в момент зарождения Qt это ещё имело какой-то смысл, но сейчас точно давно устарело
в принципе, в Qt5 можно, более-менее, обойтись без moc для своего класса.
QByteArray serializeQObject(const QObject *pObject) {
QByteArray serializedObject;
const QMetaObject* pMetaObject = pObject->metaObject();
QDataStream dataStream(&serializedObject, QIODevice::WriteOnly);
for(int i = 0; i < pMetaObject->propertyCount(); ++i) {
if(pMetaObject->property(i).isStored(pObject)) {
dataStream << pMetaObject->property(i).read(pObject);
}
}
return serializedObject;
}void deserializeObject(const QByteArray &serializedObject, QObject *pObject)
{
QDataStream dataStream(serializedObject);
QVariant variable;
const QMetaObject* pMetaObject = pObject->metaObject();
for(int i = 0; i < pMetaObject->propertyCount(); ++i) {
if(pMetaObject->property(i).isStored(pObject)) {
dataStream >> variable;
pMetaObject->property(i).write(pObject, variable);
}
}
}template<F>MyLabel: public QLabel {};, который мог бы очень даже пригодиться. Да, я конечно могу создать нечто подобно в 2 шага, с помощью создания промежуточного класса. Но вот как раз это и есть истинный неудобный костыль в чистом виде. И никаких намёков на его решение даже в самой последней версии Qt что-то не видно.Более того, современный C++ вообще позволяет решать такие вопросы намного красивее и эффективнее.
P.S. Я не говорю, что Qt идеален, в нём проблем выше крыши, вот только в альтернативах их ещё больше.
Это заблуждение. Кроссплатформенность всегда была актуальна, это только в России она не была. В Штатах, как минимум, OSX + Windows очень часто идут рука об руку.
На самом деле язык C++ не позволяет, в нынешнем виде, создать нормальную GUI библиотеку, потому что для очень многих вещей нужна интроспекция и/или поддержка паттерна observer в самом языке.
Множество != весь, не находите? Trolltech писали библиотеку и жили на продажи её, значит было кому продавать, не так ли? Да и кроссплатформенность не всегда заставляет выбирать Qt. Вы на MFC писали? Если ответ «да» и Вы не понимаете, почему кто-то бы выбрал Qt вместо него только для Windows, то продолжать разговор больше не имеет смысла.
Почему, если есть такие классные фреймворки, которые не используют MOC и код на них красивее чем у Qt, о них никто(очень мало кто) знает?
Зачем в Qt так много QMetaObject? Что такое property, invokable и прочее и как оно реализовано?
Если сигналы/слоты так легко реализовать, то сможете ли Вы найти реализацию, в которой сигнал не занимает места в объекте и может быть поставлен в очередь любого потока?
Очень даже знают. На том же wxWidgets написаны например KiCad, Code::Blocks, TrueCrypt, Password Safe, EventGhost, клиенты для Dropbox и Google Drive, интерфейсы для Maxima и CMake
А это всё и не нужно. Попытки принести динамическую интроспекцию в язык типа C++, ориентированный на быстродействия, крайне сомнительны
Очереди и потоки не имеют никакого отношения к системам типа сигнал/слот — это вообще разные вопросы
Я сравнивал Qt и MFC середины нулевых, а не сейчас. Что там было раньше, я не в курсе.
Ага, наверное из-за wxWidgets в папке dropbox лежат dll от Qt5, а в исходниках CMake папка QtDialog, посмотрел что за библиотеки загружает TrueCrypt — видимо он средствами среды пользуется(WinAPI). Дальше проверять не стал, всё и так понятно.
В Qt это часть одной системы, неразделимой. Очень удобной. К хорошему быстро привыкаешь, поэтому не надо рассказывать про «разные вопросы». Проброс событий/сигналов очень часто нужен и, в отличии от того же C#/WPF, в Qt это реализовано из коробки и очень элегантно.
thread([=]{
...
my_widget->my_slot()//???
...
}).detach();QMetaObject::invoke(my_widget, "my_slot");
Я не спорю, wxWidgets используется, но, как Вы сами понимаете, гораздо в меньшей степени, чем Qt. Когда я говорил, про никому не известные фреймворки, я, скорее, говорил о том фреймворке, на который Вы привели ссылку(что там код красивее)
Что касается примера с потоком: как минимум странно использовать std::thread вместе с Qt, ну да ладно.
Ваша задача решается одной строчкой:
QMetaObject::invoke(my_widget, «my_slot»);
https://github.com/cpp11nullptr/lsignal например здесь легко увидеть код покрасивее Qt при решение той же задачи и без всяких препроцессоров.
Модель акторов — это же естественно не просто какая-то библиотека, а скорее математическое понятие. Причём реализация этой модели может быть крайне простой и не требовать каких-то библиотек (собственно нам требуются только функции вида CreateThread, PostMessage, GetMessage, которые есть в наличие даже просто в WinAPI). Я частенько реализую подобное сам (правда более удобными способами, типа потоков из стандартной библиотеки C++ и т.п.), просто на базе потоков ОС и соответствующих системных очередей сообщений. Ну а в том же языке D модель акторов вообще встроена в сам язык.
Да, кстати, а с появлением в C++ семантики перемещения (между прочим уникальная вещь, которой не видно у других мейнстрим языков) этот метод становится ещё эффективнее. Только вот интересно, Qt про это не в курсе или нет? ) Т.е. модифицирован ли их код передачи данных через очередь так, чтобы избежать лишних копирований? ) Я не разбирался (т.к. использую для многопоточности другие инструменты, а не Qt), но вполне допускаю, что там остался старый неэффективный код.
Значит Qt сигналы слоты — это вам еще и модель акторов.
Всем implicit sharing классам и без мув семантики хорошо живется, а они в Qt практически все такие, которые являются разновидностью данных
Во вторых, с появлением в c++11 и с версии qt 5.0 были добавлены конструкторы копирования с move семантикой, но им это не особо и надо. Смотри доки для QVector например.
В каком-то смысле да. Но только весьма неудобная, т.к. ограничивает взаимодействие рамками прописанных сигналов/слотов.
Вот как раз такие схемы и являются очень нехорошими с точки зрения производительности. Потому что добавляют лишний уровень косвенности и соответственно в таком случае кэш процессора вообще ничего не может. Это один из нюансов, из-за которых Java/C# обычно серьёзно отстают от нормального C++ кода. И Qt тут действует в лучших традициях Java. Хотя в целом для GUI фреймворка это естественно простительно, т.к. на GUI почти никогда не бывает серьёзной нагрузки и соответственно рассуждать тут об оптимальности — это из области ненужной предварительной оптимизации. Но если говорить о принципах, то это как раз пример того, как не надо делать.
К примеру если я загоню в неё данные в виде своего класса, который держит данные в себе и рассчитывает на работу через перемещение.
Ну так храните указатели на них, если так принципиально и используйте RAII. Только implicit sharing по сути тоже самое RAII и есть, только реализация от вас спрятана.
В случае QueuedConnection все тоже самое, только данные будут какое то время хранится и передадутся в слот когда дойдет очередь. Используется ли там move семантика при передаче от сигнала в слот, я не знаю, нужно смотреть исходники.
Не понимаю и сейчас, вы признаетесь, что в Qt есть все что нужно, но он вам не удобен. Мне удобен.
Если мы пишем где-то vector x; (или любой другой контейнер из стандартной библиотеки C++), то это означает, что в стеке располагается несколько int'ов + указатель на реальные данные в динамической памяти. Это конечно тоже не идеал (идеал — это если бы данные прямо в стеке были, кстати, большинство реализаций std::string имеют оптимизацию, которая для малых строк размещает их прямо внутри объекта, на стеке), но работает не плохо, т.к. современные процессоры уже научились оптимизировать подобный код.
Так я про это и говорил, что я этого не знаю (как и вы оказывается), потому что использую другие механизмы для многопоточности и соответственно не проверял сам руками. Но если они это не поправили, то это может оказаться весьма неприятным сюрпризом для многих программистов, уже привыкающих к современному стандарту.
В Qt кривая архитектура по многим пунктам (и ненужный препроцессор и несоответствие современному C++ стилю и ещё много чего) и вот это несколько мешает, хотя не критично (да и всё равно конкурентов нет).
Соответственно лично я хотел бы в идеале иметь чисто GUI библиотеку (а не «фреймворк всего») с возможностями уровня Qt и таким же набором поддерживаемых платформ. При этом написанную в стиле современного C++. Это был бы идеал. Но такого к сожалению не существует, поэтому живём с чем есть — с Qt. )))
Для строк это еще более менее применимо. Но хранить вектор в стеке — это как вообще? Как это будет вообще работать в embedded, где размер стека часто бывает сильно ограничен?
Я подозреваю, если передаются implicit sharing, то разницы вообще не заметят эти самые программисты. Зато дополнительные трахи с rvalue семантикой очень можно приобрести. Меня поэтому это вообще не заботит, я кстати тоже программист С++, который не брезгует новым стандартом.
Попробуйте openFrameworks. Мне кажется вам понравится.
Так я и написал, что подобная оптимизация встречается только у std::string и только для коротких строк. Но в любом случае и std::vector и std::string с длинными строками всё равно намного эффективнее реализаций из Qt, в которых получается двойная косвенность.
Вот как раз повсеместное использование ссылочных типов — это и есть несоответствие стилю современного C++, которые ориентирован скорее на типы-значения.
Если говорить, об изменениях, которые имели негативный эффект, то совершенно точно это rvalue ссылки. Они очень мощные, но их очень сложно использовать правильно и слишком легко — неправильно. Я сам научился использовать их строго только в тех случаях, в которых я абсолютно уверен.— цитата от сюда. Сами создатели стандарта это утверждают.
Так там же как раз есть всё, кроме GUI библиотеки. )))
Эффективнее тем, что память выделяется в стеке, а не в куче? А есть какие нибудь статьи которые сравнивают производительность хранения в этих областях памяти? И вообще, это так принципиально хранить строки крайне производительно?
Просто забудьте как устроен implicit sharing и думайте что в qt move семантика повсеместна. Потому что это практически одно и тоже по производитльности (дайте мне бенчмарки где это опровергается).
Есть, свои контролы которые отрисовываются в opengl.
In summary, QVarLengthArray is a low-level optimization class that only makes sense in very specific cases. It is used a few places inside Qt and was added to Qt's public API for the convenience of advanced users.
std_vector[0] превращается компилятором в *(std_vector.data_pointer), то строка вида qvector[0] будет превращаться в *(qvector.shared_pointer->data_pointer), что уже плохо для процессора.
Наконец то я понял о какой производительности идет речь.
А теперь из той же документации:
Нет, у QVector вместо оператора [], нужно вызывать метод at для извлечения элементов, он напрямую обращается к памяти по указателю. Можете посмотреть исходники QVector и проверить. Это идет в разрез с устоявшемся в stl operator [] и методом at, где смысл примерно наоборот этих методов, но контейнеры qt были когда stl еще не был стандартом.
#include <QCoreApplication>
#include <QVector>
#include <vector>
#include <QElapsedTimer>
#include <QDebug>
int main()
{
const int vectorSize = 500 * 1000 * 1000;
int summer = 0;
QVector<int> qtVector(vectorSize, 0);
std::vector<int> stlVector(vectorSize, 0);
QElapsedTimer timer;
timer.restart();
for (int i = 0; i < vectorSize; i++)
{
summer += qtVector.at(i);
}
qDebug() << "QVector::at(i)" << timer.elapsed();
timer.restart();
for (auto iterator = qtVector.constBegin(); iterator != qtVector.constEnd(); iterator++)
{
summer += *iterator;
}
qDebug() << "QVector::iterators" << timer.elapsed();
timer.restart();
for (int i = 0; i < vectorSize; i++)
{
qtVector[i] = i;
}
qDebug() << "QVector filling" << timer.elapsed();
timer.restart();
for (auto iterator = qtVector.begin(); iterator != qtVector.end(); iterator++)
{
*iterator = summer;
}
qDebug() << "QVector filling via iterators" << timer.elapsed();
timer.restart();
for (int i = 0; i < vectorSize; i++)
{
summer += stlVector[i];
}
qDebug() << "std::vector::operator[]" << timer.elapsed();
timer.restart();
for (auto iterator = stlVector.cbegin(); iterator != stlVector.cend(); iterator++)
{
summer += *iterator;
}
qDebug() << "std::vector::iterators" << timer.elapsed();
timer.restart();
for (int i = 0; i < vectorSize; i++)
{
stlVector[i] = i;
}
qDebug() << "std::vector filling" << timer.elapsed();
timer.restart();
for (auto iterator = stlVector.begin(); iterator != stlVector.end(); iterator++)
{
*iterator = summer;
}
qDebug() << "std::vector filling via iterators" << timer.elapsed();
return 0;
}QVector::at(i) 357
QVector::iterators 352
QVector filling 503
QVector filling via iterators 484
std::vector::operator[] 341
std::vector::iterators 498
std::vector filling 316
std::vector filling via iterators 936
QVector::operator[] 1077
QVector::at(i) 134
QVector::iterators 140
QVector filling 1094
QVector filling via iterators 806
std::vector::operator[] 136
std::vector::iterators 139
std::vector filling 228
std::vector filling via iterators 230
#include <iostream>
#include <chrono>
#include <vector>
#include <QVector>
using namespace std;
using namespace chrono;
int main()
{
const size_t size=500'000'000;
QVector<int> qt_vector;
vector<int> stl_vector;
auto start=high_resolution_clock::now();
for(auto i=0; i<size; i++) stl_vector.push_back(i);
cout<<"stl vector push_back: "<<duration_cast<milliseconds>(high_resolution_clock::now()-start).count()<<endl;
start=high_resolution_clock::now();
for(auto i=0; i<size; i++) qt_vector.push_back(i);
cout<<"qt vector push_back: "<<duration_cast<milliseconds>(high_resolution_clock::now()-start).count()<<endl;
}Хотя потом естественно всё быстро разъяснилось (кстати, кому-то требуются пояснения?)
stl vector push_back: 1089
qt vector push_back: 1043
А значения то в итоге какие получились? Этот пример тоже не корректен, т.к. у stl вектора и qvector могут быть разные правила увеличения capacity.
Я кстати прогнал тест для цифры 200000000, вот мои результаты:
У QVector есть ограничение на максимальный размер, можно посмотреть например здесь — QTBUG-33019
А если зарезервировать память заранее в 500000000 то bad alloc не возникает. Что в принципе и нужно делать при работе с таким большим количеством данных.
Но это на самом деле вообще не имеет значения на фоне обнаруженного. )))Отнюдь. Вы получили сбой в искусственном тесте, только и всего. Вероятность его возникновения в реальных условиях невысока. Хотя мне тоже непонятно, почему решили ввести такое ограничение, это не трагедия.
В противном случае надо хорошенько подумать о замене простым массивом
Потому что если строка вида std_vector[0] превращается компилятором в *(std_vector.data_pointer), то строка вида qvector[0] будет превращаться в *(qvector.shared_pointer->data_pointer), что уже плохо для процессора.
Костылик для сигнал-слот системы в Qt