Pull to refresh

Comments 94

Ну, что тут сказать, все так и есть.
Для полноты картины, почему так и почему до сих пор не сделали поддержку темплейтов в Qt, советую почитать два треда в dev мейл рассылке Qt:
1. Стартует тут — http://lists.qt-project.org/pipermail/development/2016-February/024990.html
2. Вторая продолжается уже тут — http://lists.qt-project.org/pipermail/development/2016-March/025063.html

Вкратце, все ждут поддержку новых стандартов уровня с++17, которые отчасти уже реализованы clang.
Кстати, только сейчас 5.6 будет последний релиз который отчасти еще будет поддерживать компиляторы без С++11.
We are planning to upgrade qdoc to use clang for parsing C++

Ха! Как раз думал когда про MOC статейки читал — почему они в MOC clang не юзают. А они, оказывается, ждут его современную реализацию. Нет чтобы масштабируемость предусмотреть (всё равно ведь придётся) и сделать с тем, что есть!.. Или там есть какая-то разумная причина?
Если ждут C++17, то можно смело переходить на ожидание C++20. С++17 будет обрубком, а не стандартом.
Я бы его вообще отменял в том виде, каким его сейчас рисуют. Оттуда выбросили решительно всё, чего ждало сообщество. Сказали бы сразу "ждите С++20" и всё на этом.
Я бы тоже. Мне вообще не понятно, кому он нужен в том виде, в котором его собираются выпустить, и как это согласуется с major/minor чередованием. C++17, в этом виде, это не более чем minor, а minor уже был.
Если в классе только слоты и нет сигналов, и используется new signal/slots syntax (connect в компайл-тайм к указателю на метод / лямбде вместо макросов SIGNAL/SLOT), то класс не должен иметь макрос Q_OBJECT и для него не будет сгенерирован *_moc.cpp.
Спасибо! Почитал.

connect(sender, &Sender::valueChanged, receiver, &Receiver::updateValue );

Выглядит неплохо и понятно как работает. Действительно — всё напрямую, почему бы нет. Просто храним в QObject мапу, где ключом выступает адрес метода-слота, а значением — массив подписчиков-сигналов. Завтра попробую и добавлю в статью.
Изменил статью с учётом вашего комментария. Спасибо!
Не за что! Я этого прямым текстом нигде не видел, чтоб было описано. Это я заметил сам в процессе экспериментов, и теперь активно пользуюсь. Удаление лишних Q_OBJECT существенно ускоряет компиляцию проектов с большим количеством исходников, да и connect во время компиляции с нормальной проверкой ошибок — это большой плюс.
А почему нельзя сделать класс, наследующий QObject с виртуальным слотом, а он него произвести шаблон? По-моему, это должно работать. Как-то так:
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; }
};

Да, это действительно сработает. Но если делать так для каждого шаблонного класса в проекте — кода будет больше, чем если сделать мини-класс для обёртки основных событий и дальше использовать их где угодно...

На самом деле, я об этом не написал в статье, но в моей библиотеке есть ещё специфика, заключающаяся в том, что я стараюсь писать код с минимальной зависимостью от конкретной реализации GUI. Отрабатывают универсальную концепцию организации GUI. Поэтому для меня возможность использовать такие костылики-обёртки позволяет ещё минимизировать количество Qt-специфических вещей в универсальном, не зависимом от конкретной реализации коде.
Да, то, что Qt реально не умеет шаблоны — это следствие их общего кривого подхода с модификацией языка своим никчемным препроцессором (хотя в момент зарождения Qt это ещё имело какой-то смысл, но сейчас точно давно устарело). Однако если вам реально нужно только это
В общем, с учётом того, что от MOC-системы мне была нужна лишь возможность подписываться на события через статический метод QObject::connect(...), я решил написать небольшой костыль
, то тогда я не вижу смысла городить все эти ужасы с макросами, классами и т.п. Весь Qt позволяет подписываться на события и просто функциями. А учётом того, что этой функций может быть и лямбда, мы легко получаем механизм подписки на события любым своим (не входящий в метасистему Qt и соответственно хоть с шаблонами, хоть с чем угодно) классом в виде конструкции connect(sender, signal, [=]{reciver_object.handler();}).
Ух ты. Спасибо, странно что такое не нагуглилось в процессе поиска решения проблемы. Попробую, и добавлю тоже в статью завтра. Это та самая фича, которая начиная с пятой версии появилась (о чём VioletGiraffe писал выше)?
Блин. Прогуглил внимательнее — таки да… Всё потому, что кто-то совсем не молодец и поленился нажать дальше второй ссылки по запросу "qt signal slot"… Опять, короче, извиняюсь перед всеми за то, что неправильно изучил материал.

Завтра добавлю эту инфу в статью.
Так бывает, просто нет смысла писать статьи будучи знакомым с большим инструментом всего 2 недели. Проще спросить на SO, к примеру, чем городить свои огороды раньше времени.
Через две недели писать точно не стоит, да и сам формат статьи "Я реализовал отличные костыли" обычно никуда не годится, но я все равно благодарен автору и комментирующим — хоть и пишу на Qt давно, но не знал, что с новым синтаксисом сигналов-слотов можно решить такую вот проблему шаблонного хэндлера (которая, к слову, у меня никогда не возникала).
Кстати, я написал этот комментарий, находясь в ста метрах от того места, где "этот мужик выше" размахивал костылями. Ни на что не намекаю, просто как факт :)
А я открыл эту статью как раз по-этому. Ожидая, что автор нагородит каких-нибудь кривостей и в комментариях ему подскажут правильный путь. Собственно и не ошибся. Это я к тому, что такие статьи тоже стоит писать.
хотя в момент зарождения Qt это ещё имело какой-то смысл, но сейчас точно давно устарело

именно что тогда оно и имело. И именно из-за устаревания, из-за нововведения C++11 (5 лет, это не сильно давно, учитывая темпы развития C++) в виде лямбд они таки запилили новый синтаксис в Qt5. Тот же QtC вовсю переходит на использование нового синтаксиса у себя в кодовой базе. Старый вариант — для совместимости.

Плюс сам вызов moc-компилятора нужен, если мне не изменяет память, только для Q_OBJECT и только для сигнал-слотов. Остальные плюшки Meta-object модели доступны и так.
Не только для сигал слотов, еще он нужен для мета информации о классе.
Ну да, вроде для динамических пропертей ещё. Давно разбирался, и не сильно пользовался. Но многое из метаобъектной модели обходится макросной магией, и, в принципе, в Qt5 можно, более-менее, обойтись без moc для своего класса.
Как раз недавно наткнулся на статью, где рассказывалось как с помощью мета-объектной информации можно намутить свою хитровылепленную сериализацию объектов для удалённого использования с возможностью синхронизации копий объектов из коробки. Там ребята вообще сделали так, что MOC кусок их кода встраивал в генерируемый код.
в принципе, в Qt5 можно, более-менее, обойтись без moc для своего класса.

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

Кстати, вот ещё одна существенная разница между их кривой реализаций системы сигнал/слот и нормальной (к которой они вроде как пытаются перейти, хотя это надо было ещё лет 20 назад сделать). Их вариант не проверяется компилятором, т.е. можно сделать неверный connect, который ругнётся только в процессе исполнения. А в случае нормальной системы такое невозможно — всё будет отслеживаться на стадии компиляции.
Если все нужные мемберы объявить через qt property то сериализация будет сводиться к:

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);
        }
    }
}
Это ничем не отличается от реализации на обычных макросах (их полно в разных библиотеках) — точно такой же костыль. Только ещё и требующий отдельного препроцессора.

Нормально решение появится только после введения в C++ статической интроспекции, как в D. А до той поры приходится пользоваться костылями. Что с Qt, что без. Собственно лично для меня это единственный крупный недостаток C++ и я очень надеюсь что он будет устранён как можно скорее (хорошо бы в C++17).
Может и так. Только мне не нужно ломать голову над макросами.
Препроцессор мне не мешает. А вот безумно долго компиляющийся буст, и подозреваю какие нибудь крутые макросы, бывает очень мешает.
Я говорил скорее о 90-ых годах))) Так что смысла это не имеет не 5 лет, а уже скоро 20 будет. )))

Вообще любые препроцессоры языка — это зло в чистом виде. По целому ряду причин. Их можно долго перечислять, начиная с проблем в редакторах/IDE. Однако в некоторых редких случаях по другому просто нельзя. Например как в случае AspectC++ или ODB. Хотя если в C++17 введут статическую интроспекцию, то и в системах типа ODB это тоже перестанет быть нужным. Да и как показывает пример sqlpp11, уже даже в текущем стандарте можно выкручиваться (причём получать при этом результат мощнее чем скажем в C#), правда ценой некоторых извращений внутри.

Однако в случае Qt, очевидно, что никакой реальной потребности в препроцессоре не было. Это видно и по множеству существующих без этого конкурирующих кроссплатформенных GUI библиотек (wxWidget, GTK+ и т.п.) и по множеству просто библиотек типа сигнал/слот (тот же Boost.Signals2) — никто из них не требует обязательного наличия компилятора с поддержкой C++11, не говоря уже о каких-то препроцессорах.

Но, если уж браться за реализацию подобной кривой архитектуры на базе препроцессора, то надо хотя бы это сделать полноценно. А даже с этим в Qt не справились — например их препроцессор не может шаблоны. Причём речь не о какой-то магии метапрограммирования в стиле Александреску, а о банальном параметризованном виджете типа
template<F>MyLabel: public QLabel {};
, который мог бы очень даже пригодиться. Да, я конечно могу создать нечто подобно в 2 шага, с помощью создания промежуточного класса. Но вот как раз это и есть истинный неудобный костыль в чистом виде. И никаких намёков на его решение даже в самой последней версии Qt что-то не видно.
Ага, «конкурирующих». Много они наконкурировали с Qt? Где Qt и где всё остальное? Сейчас есть ответвление Qt(copperspice), в котором нет MOC, но выглядит уж очень спорно и это при том, что они используют современные возможности C++, которых не было и в помине, когда MOC писался.

P.S. boost.signals2 это ужасное порождение, ничего общего с простой системой сигналов/слотов не имеющая. И я не говорю уже о интроспекции и прочих вещах, которые на MOC опираются.
Сейчас реальных конкурентов действительно нет. Но это вовсе не следствие качества реализации Qt. Это следствие захвата Android'ом мира в сочетание с тем фактом, что в Qt успели (ну да, хорошо когда есть большая команда на зарплате, а не несколько энтузиастов как у других библиотек) сделать под него порт. Однако это не отменяет внутреннюю кривизну их архитектуры. Более того, если мне вдруг понадобится сделать приложение, которому достаточно будет работать только на десктoпах (Windows/OSX/Linux), то я предпочту взять другую библиотечку, типа того же wxWidgets.

Да, а насчёт boost.signals2 — это был пример для тех, кому реальна нужна специальная система сигнал/слот. А для GUI фреймворка это совсем не обязательно (в том же wxWidgets функция connect есть, а какой-то специальной системы сигнал/слотов нет). Более того, современный C++ вообще позволяет решать такие вопросы намного красивее и эффективнее.
Qt был самым популярным GUI-фреймворком, когда Android не было и в проекте. И это именно за счёт расширения языка. Сигналы в Qt удобнее чем родные event в C#, что уже говорить о поделках других библиотек для C++? Вообще, это, конечно, чистая вкусовщина, но Qt объективно более популярен и это началось не вчера и, на мой взгляд, это как раз из-за удобства его использования, львиную долю которого даёт QObject и MOC.

Более того, современный C++ вообще позволяет решать такие вопросы намного красивее и эффективнее.

Каким образом?

P.S. Я не говорю, что Qt идеален, в нём проблем выше крыши, вот только в альтернативах их ещё больше.
В те времена, когда Андроида не было и в проекте, кроссплатформенность вообще была не актуальна и большинство приложений создавалось на родных для платформы фреймворках (типа всяких MFC/WTL и т.п.) или вообще на голом API/самописных велосипедах.

P.S. Я не говорю, что Qt идеален, в нём проблем выше крыши, вот только в альтернативах их ещё больше.

На самом деле имеются вполне красивые архитектурно (с точки зрения C++) GUI библиотеки. Только ими пользоваться невозможно из-за малого набора функциональности или малого числа поддерживаемых платформ. Если бы за какой-нибудь из них хотя бы лет 5 постояла бы серьёзная компания с деньгами, то возможно мы бы и получили настоящее решение. А сейчас на практике имеем только Qt...
Это заблуждение. Кроссплатформенность всегда была актуальна, это только в России она не была. В Штатах, как минимум, OSX + Windows очень часто идут рука об руку.

На самом деле язык C++ не позволяет, в нынешнем виде, создать нормальную GUI библиотеку, потому что для очень многих вещей нужна интроспекция и/или поддержка паттерна observer в самом языке.
Это заблуждение. Кроссплатформенность всегда была актуальна, это только в России она не была. В Штатах, как минимум, OSX + Windows очень часто идут рука об руку.

Интересно, с чего это тогда множество дорогого и качественного не русского софта в то время имело версию только под Windows? ) Да и со многим до сих пор так остаётся, хотя некоторые всё же начали выпускать под другие платформы.

На самом деле язык C++ не позволяет, в нынешнем виде, создать нормальную GUI библиотеку, потому что для очень многих вещей нужна интроспекция и/или поддержка паттерна observer в самом языке.

Эээ что? ) Зачем для GUI нужна интроспекция? ) Я понимаю зачем она для сериализации или ещё некоторых специальных задач. Но для GUI то зачем? ))) Ну а про наблюдателя — это вообще смешно, он же реализуется в несколько строк на почти любом языке.

http://ultimatepp.org/www$uppweb$comparison$en-us.html например здесь легко увидеть код покрасивее Qt при решение той же задачи и без всяких препроцессоров.
Множество != весь, не находите? Trolltech писали библиотеку и жили на продажи её, значит было кому продавать, не так ли? Да и кроссплатформенность не всегда заставляет выбирать Qt. Вы на MFC писали? Если ответ «да» и Вы не понимаете, почему кто-то бы выбрал Qt вместо него только для Windows, то продолжать разговор больше не имеет смысла.

В остальном, я не хочу с Вами спорить, т.к. мне совершенно не интересно в чём то Вас убеждать. Оставлю Вам только несколько вопросов, на которые не нужно отвечать, они для Вас — повод для размышлений.

Почему, если есть такие классные фреймворки, которые не используют MOC и код на них красивее чем у Qt, о них никто(очень мало кто) знает?
Зачем в Qt так много QMetaObject? Что такое property, invokable и прочее и как оно реализовано? Как работает QML?
Если сигналы/слоты так легко реализовать, то сможете ли Вы найти реализацию, в которой сигнал не занимает места в объекте и может быть поставлен в очередь любого потока?
Множество != весь, не находите? Trolltech писали библиотеку и жили на продажи её, значит было кому продавать, не так ли? Да и кроссплатформенность не всегда заставляет выбирать Qt. Вы на MFC писали? Если ответ «да» и Вы не понимаете, почему кто-то бы выбрал Qt вместо него только для Windows, то продолжать разговор больше не имеет смысла.

Сравнивать MFC с современным Qt явно некорректно. А если сравнить MFC с Qt из 90-ых годов, то ещё не известно в чью пользу будет сравнение. )))

Почему, если есть такие классные фреймворки, которые не используют MOC и код на них красивее чем у Qt, о них никто(очень мало кто) знает?

Очень даже знают. На том же wxWidgets написаны например KiCad, Code::Blocks, TrueCrypt, Password Safe, EventGhost, клиенты для Dropbox и Google Drive, интерфейсы для Maxima и CMake. Это только то, чем я лично пользовался. А наверняка там ещё сотни известных приложений есть, которые я никогда не видел. Конечно на Qt побольше известных продуктов будет, но за wxWidgets никогда не стояло сильной компании, финансирующей разработку и рекламу.

Зачем в Qt так много QMetaObject? Что такое property, invokable и прочее и как оно реализовано?

А это всё и не нужно. Попытки принести динамическую интроспекцию в язык типа C++, ориентированный на быстродействия, крайне сомнительны. Для этого у нас уже есть Java и C# с их рефлексией и тормозами. C++ нужна статическая интроспекция как в D и я надеюсь когда-нибудь она там появится. Были даже разговоры о появление в C++17.

Если сигналы/слоты так легко реализовать, то сможете ли Вы найти реализацию, в которой сигнал не занимает места в объекте и может быть поставлен в очередь любого потока?

Очереди и потоки не имеют никакого отношения к системам типа сигнал/слот — это вообще разные вопросы. От системы сигнал/слот в случае многопоточности требуется только безопасность, а не реализация своих очередей и т.п. Если же мы хотим заняться реализацией многопоточности через обмен сообщения между потоками (т.е. использовать модель акторов — лично мне она очень нравится), то для этого есть соответствующие удобные инструменты, типа того же CAF.
Я сравнивал Qt и MFC середины нулевых, а не сейчас. Что там было раньше, я не в курсе.

Очень даже знают. На том же wxWidgets написаны например KiCad, Code::Blocks, TrueCrypt, Password Safe, EventGhost, клиенты для Dropbox и Google Drive, интерфейсы для Maxima и CMake

Ага, наверное из-за wxWidgets в папке dropbox лежат dll от Qt5, а в исходниках CMake папка QtDialog, посмотрел что за библиотеки загружает TrueCrypt — видимо он средствами среды пользуется(WinAPI). Дальше проверять не стал, всё и так понятно.

А это всё и не нужно. Попытки принести динамическую интроспекцию в язык типа C++, ориентированный на быстродействия, крайне сомнительны

Нужно, не нужно — это не Вам решать. Это удобно. Если удастся сделать всё то же самое, то можно и статической обойтись. В целом, я понимаю, что динамической в C++ не будет и не уверен, что она нужна. Но вот в Qt это даёт просто потрясающие возможности связи с QML.

Очереди и потоки не имеют никакого отношения к системам типа сигнал/слот — это вообще разные вопросы

В Qt это часть одной системы, неразделимой. Очень удобной. К хорошему быстро привыкаешь, поэтому не надо рассказывать про «разные вопросы». Проброс событий/сигналов очень часто нужен и, в отличии от того же C#/WPF, в Qt это реализовано из коробки и очень элегантно.
Я сравнивал Qt и MFC середины нулевых, а не сейчас. Что там было раньше, я не в курсе.

Microsoft полностью забили на развитие C++ и всего с ним связанного после выхода .Net — оно же тогда позиционировалось как замена не только Java, а вообще всего, включая нативный код (даже OS будущего они планировали на C#). Так что всю их продукцию связанную с C++ в период где-то с 2001-го по 2013-ый можно смело игнорировать — там была только имитация деятельности. А вот в 90-ые с MFC конкурировал разве что какой-нибудь C++Builder, а Qt и wxWidgets были странными поделками.

Ага, наверное из-за wxWidgets в папке dropbox лежат dll от Qt5, а в исходниках CMake папка QtDialog, посмотрел что за библиотеки загружает TrueCrypt — видимо он средствами среды пользуется(WinAPI). Дальше проверять не стал, всё и так понятно.

Да, с cmake-gui я похоже перепутал что-то. Что же касается dropbox, то изначально он был написан на Питоне и соответственно использовал wxWidgets (в виде wxPython). А совсем недавно (причём я уже давно перестал им пользоваться) они переписали его на C++ и взяли уже Qt для GUI. Ну а с truecrypt вообще всё просто — wxWidgets же гораздо чаще линкуют статически, т.к. там нет вопросов с лицензии, как у Qt. Но тут легко можно глянуть на исходники: https://github.com/FauxFaux/truecrypt/blob/targz/Main/SystemPrecompiled.h

Ну а вообще, чтобы не ориентироваться на мою память, можно просто глянуть например сюда https://en.wikipedia.org/wiki/Category:Software_that_uses_wxWidgets и думаю вся аргументация о неиспользование этой библиотеки пройдёт.

В Qt это часть одной системы, неразделимой. Очень удобной. К хорошему быстро привыкаешь, поэтому не надо рассказывать про «разные вопросы». Проброс событий/сигналов очень часто нужен и, в отличии от того же C#/WPF, в Qt это реализовано из коробки и очень элегантно.

Да ничего оно не удобное. Вот например запускаю я свой поток стандартным для современного C++ способом и хочу послать из него сообщение в виджет (естественно живущий в основном gui потоке):

thread([=]{
    ...
    my_widget->my_slot()//???
    ...
}).detach();

И как мне это сделать? Ну т.е. я конечно же знаю способ, через создание дополнительной функции в том виджете, плюс объявление нового сигнала и т.п. Но это опять же классический кривой костыль. Потому как для реальной посылки сообщения ничего этого не требуется.
Я не спорю, wxWidgets используется, но, как Вы сами понимаете, гораздо в меньшей степени, чем Qt. Когда я говорил, про никому не известные фреймворки, я, скорее, говорил о том фреймворке, на который Вы привели ссылку(что там код красивее)

Что касается примера с потоком: как минимум странно использовать std::thread вместе с Qt, ну да ладно. Ваша задача решается одной строчкой:
QMetaObject::invoke(my_widget, "my_slot");

(по памяти, поэтому где-то может быть ошибка, но суть должна быть ясна)
Я не спорю, wxWidgets используется, но, как Вы сами понимаете, гораздо в меньшей степени, чем Qt. Когда я говорил, про никому не известные фреймворки, я, скорее, говорил о том фреймворке, на который Вы привели ссылку(что там код красивее)

Ну так и в wxWidgets не требуется никаких препроцессоров для работы. ) Что касается того фреймворка, то и его используют, но ещё меньше чем wxWidgets. Ну он и сам слабее (по функциональности) и у них там крайне печальный список поддерживаемых платформ.Хотя я сейчас глянул у них на сайте — похоже они подбираются к портированию на Андроид. Если успеют раньше того же wxWidgets, то возможно ещё что-то получится у них. )

Что касается примера с потоком: как минимум странно использовать std::thread вместе с Qt, ну да ладно.

С удовольствием использую QThread или что там ещё, если мне кто-то покажет аналогичный синтаксис с ним (чтобы не надо было плодить классы на каждый чих).

Ваша задача решается одной строчкой:
QMetaObject::invoke(my_widget, «my_slot»);

Ааа, это снова то самое, когда мы пытаемся сделать из C++ скриптовой язык и тем самым теряем вообще все преимущества компилируемого? ) Ведь в эту хрень же можно любые данные передать и компилятор проглотит. А потом приложение будет ругаться, в лучшем случае у тестировщиков, а в худшем у клиентов.
Да, я забыл ответить на вопрос по поводу современной реализации сигналов/слотов. Ну например вот https://github.com/cpp11nullptr/lsignal симпатичный вариант. Но вообще это всё настолько простые вещи, что мне непонятно откуда тут могут возникнуть сложности и зачем оформлять подобное в отдельную библиотеку.
https://github.com/cpp11nullptr/lsignal например здесь легко увидеть код покрасивее Qt при решение той же задачи и без всяких препроцессоров.

Очередей нет, как и в любой другой альтернативной реализации сигналов и слотов
Очереди нужны только для реализации многопоточности, а это уже совсем другая задача, не имеющая отношения к системе сигнал/слот. В случае необходимости реализации обмена сообщениями между потоками я возьму соответствующее профессиональное решение типа CAF (https://www.actor-framework.org), которое опять же на порядки удобнее и эффективнее любых поделок на эту тему в Qt.
Нет, не только для многопоточности конечно же. Можете посмотреть мою публикацию для чего например.

Или например Кооперативная многозадачность. Да вообщем все то, для чего нужен libevent. По сути один механизм, полезный и для обмена сообщениями, и для многопоточных приложений и для кооперативной многозадачности без необходимости еще одного профессионального решения. CAF я открыл и закрыл сразу, может быть где не будет Qt я когда нибудь это заюзаю, но куда уж проще для обмена между потоками использовать future и собственную реализацию очереди чем тянуть еще одну крутую библиотеку.
Модель акторов — это же естественно не просто какая-то библиотека, а скорее математическое понятие. Причём реализация этой модели может быть крайне простой и не требовать каких-то библиотек (собственно нам требуются только функции вида CreateThread, PostMessage, GetMessage, которые есть в наличие даже просто в WinAPI). Я частенько реализую подобное сам (правда более удобными способами, типа потоков из стандартной библиотеки C++ и т.п.), просто на базе потоков ОС и соответствующих системных очередей сообщений. Ну а в том же языке D модель акторов вообще встроена в сам язык.

Что касается CAF, то это просто одна из самых мощных и эффективных реализаций, подходящая скорее для совсем серьёзных задач.

В любом случае реализация обмена информацией между потоками (системными или зелёными, не важно) через сообщения — это очень хороший метод. Только вот причём тут Qt?) Там это реализовано далеко не так уж удобно, потому что они зачем-то объединили это с системой сигнал/слот, которая при взаимодействие потоков обычно вообще не нужна.

Да, кстати, а с появлением в C++ семантики перемещения (между прочим уникальная вещь, которой не видно у других мейнстрим языков) этот метод становится ещё эффективнее. Только вот интересно, Qt про это не в курсе или нет? ) Т.е. модифицирован ли их код передачи данных через очередь так, чтобы избежать лишних копирований? ) Я не разбирался (т.к. использую для многопоточности другие инструменты, а не Qt), но вполне допускаю, что там остался старый неэффективный код.
Модель акторов — это же естественно не просто какая-то библиотека, а скорее математическое понятие. Причём реализация этой модели может быть крайне простой и не требовать каких-то библиотек (собственно нам требуются только функции вида CreateThread, PostMessage, GetMessage, которые есть в наличие даже просто в WinAPI). Я частенько реализую подобное сам (правда более удобными способами, типа потоков из стандартной библиотеки C++ и т.п.), просто на базе потоков ОС и соответствующих системных очередей сообщений. Ну а в том же языке D модель акторов вообще встроена в сам язык.

Значит Qt сигналы слоты — это вам еще и модель акторов.

Да, кстати, а с появлением в C++ семантики перемещения (между прочим уникальная вещь, которой не видно у других мейнстрим языков) этот метод становится ещё эффективнее. Только вот интересно, Qt про это не в курсе или нет? ) Т.е. модифицирован ли их код передачи данных через очередь так, чтобы избежать лишних копирований? ) Я не разбирался (т.к. использую для многопоточности другие инструменты, а не Qt), но вполне допускаю, что там остался старый неэффективный код.

  1. Всем implicit sharing классам и без мув семантики хорошо живется, а они в Qt практически все такие, которые являются разновидностью данных
  2. Во вторых, с появлением в c++11 и с версии qt 5.0 были добавлены конструкторы копирования с move семантикой, но им это не особо и надо. Смотри доки для QVector например.

Вообще я что-то начинаю подозревать что вы не очень хорошо знакомы с кодом Qt, а так же его внутренней реализацией. Как вы вообще понимаете что в Qt старый неэффективный код?
Значит Qt сигналы слоты — это вам еще и модель акторов.

В каком-то смысле да. Но только весьма неудобная, т.к. ограничивает взаимодействие рамками прописанных сигналов/слотов.

Всем implicit sharing классам и без мув семантики хорошо живется, а они в Qt практически все такие, которые являются разновидностью данных

Вот как раз такие схемы и являются очень нехорошими с точки зрения производительности. Потому что добавляют лишний уровень косвенности и соответственно в таком случае кэш процессора вообще ничего не может. Это один из нюансов, из-за которых Java/C# обычно серьёзно отстают от нормального C++ кода. И Qt тут действует в лучших традициях Java. Хотя в целом для GUI фреймворка это естественно простительно, т.к. на GUI почти никогда не бывает серьёзной нагрузки и соответственно рассуждать тут об оптимальности — это из области ненужной предварительной оптимизации. Но если говорить о принципах, то это как раз пример того, как не надо делать.

Во вторых, с появлением в c++11 и с версии qt 5.0 были добавлены конструкторы копирования с move семантикой, но им это не особо и надо. Смотри доки для QVector например.

Да, действительно при реализации implicit sharing реализация семантики перемещения у контейнеров Qt не принципиально. Но я немного про другое. Я говорю про готовность для работы с перемещением их очереди сообщений. К примеру если я загоню в неё данные в виде своего класса, который держит данные в себе и рассчитывает на работу через перемещение.
В каком-то смысле да. Но только весьма неудобная, т.к. ограничивает взаимодействие рамками прописанных сигналов/слотов.

Любая технология ограничена какими то рамками. И Qt не исключение.

Вот как раз такие схемы и являются очень нехорошими с точки зрения производительности. Потому что добавляют лишний уровень косвенности и соответственно в таком случае кэш процессора вообще ничего не может. Это один из нюансов, из-за которых Java/C# обычно серьёзно отстают от нормального C++ кода. И Qt тут действует в лучших традициях Java. Хотя в целом для GUI фреймворка это естественно простительно, т.к. на GUI почти никогда не бывает серьёзной нагрузки и соответственно рассуждать тут об оптимальности — это из области ненужной предварительной оптимизации. Но если говорить о принципах, то это как раз пример того, как не надо делать.

Ну так храните указатели на них, если так принципиально и используйте RAII. Только implicit sharing по сути тоже самое RAII и есть, только реализация от вас спрятана.

К примеру если я загоню в неё данные в виде своего класса, который держит данные в себе и рассчитывает на работу через перемещение.

Ну естественно, если вы передаете в вызове сигнала стековую временную переменную, или явно используете move, то объект передасться через move семантику, т.к. сигнал это по сути просто вызов функции слота в случае Direct Connection. В случае QueuedConnection все тоже самое, только данные будут какое то время хранится и передадутся в слот когда дойдет очередь. Используется ли там move семантика при передаче от сигнала в слот, я не знаю, нужно смотреть исходники.

На самом деле я уже утратил интерес к дискуссии. Мне всегда была интересна мотивация этой распространенной позиции, типа qt только для интерфейсов, особенно когда приходиться разгребать всю эту свалку библиотек от такого подхода (о, как это все еще и компилировать вместе, отдельная, сказочная история) и я не понимал зачем это нужно.

Не понимаю и сейчас, вы признаетесь, что в Qt есть все что нужно, но он вам не удобен. Мне удобен.
Ну так храните указатели на них, если так принципиально и используйте RAII. Только implicit sharing по сути тоже самое RAII и есть, только реализация от вас спрятана.

Ничего подобного.

Если мы пишем где-то vector x; (или любой другой контейнер из стандартной библиотеки C++), то это означает, что в стеке располагается несколько int'ов + указатель на реальные данные в динамической памяти. Это конечно тоже не идеал (идеал — это если бы данные прямо в стеке были, кстати, большинство реализаций std::string имеют оптимизацию, которая для малых строк размещает их прямо внутри объекта, на стеке), но работает не плохо, т.к. современные процессоры уже научились оптимизировать подобный код.

Если же мы пишем где-то QString x; (или большинство других контейнеров из Qt), то это означает, что в стеке расположен указатель на некоторую область динамической памяти, в которой расположены несколько Int'ов и указатель на другую область динамической памяти, в которой уже находятся реальные данные. И вот такую двойную косвенность процессоры оптимизировать уже не могут. Кстати, именно по этой же технологии реализованы все классы в Java/C# и поэтому же они заметно медленнее C++ аналогов.

Да, а аналогом данного Qt класса в C++ будет что-то вроде shared_ptr< string>. Такое тоже изредка используется для особых случаев, но естественно нет повсеместного использования подобного.

В целом подход из Qt раньше имел и определённые плюс, помимо указанных выше минусов. Это мгновенная скорость реализации присвоения. Ну точно она в итоге получается мгновенной, если не будем модифицировать другие копии (а это как раз самый распространённый сценарий — обычно другие копии вообще умирают). Однако с приходом в C++ семантики перемещения у данного подхода уже больше нет никаких плюсов, а остались одни только минусы.

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

Так я про это и говорил, что я этого не знаю (как и вы оказывается), потому что использую другие механизмы для многопоточности и соответственно не проверял сам руками. Но если они это не поправили, то это может оказаться весьма неприятным сюрпризом для многих программистов, уже привыкающих к современному стандарту.

Не понимаю и сейчас, вы признаетесь, что в Qt есть все что нужно, но он вам не удобен. Мне удобен.

  1. В Qt есть всё что мне нужно от GUI библиотеки.
  2. В Qt есть все необходимые мне платформы.
  3. В Qt есть ещё много чего, лично мне абсолютно не нужного (в том смысле что я использую другие реализации этого же, например из Boost'a), но это не особо страшно, т.к. оно не мешает.
  4. В Qt кривая архитектура по многим пунктам (и ненужный препроцессор и несоответствие современному C++ стилю и ещё много чего) и вот это несколько мешает, хотя не критично (да и всё равно конкурентов нет).

Соответственно лично я хотел бы в идеале иметь чисто GUI библиотеку (а не "фреймворк всего") с возможностями уровня Qt и таким же набором поддерживаемых платформ. При этом написанную в стиле современного C++. Это был бы идеал. Но такого к сожалению не существует, поэтому живём с чем есть — с Qt. )))
Если мы пишем где-то vector x; (или любой другой контейнер из стандартной библиотеки C++), то это означает, что в стеке располагается несколько int'ов + указатель на реальные данные в динамической памяти. Это конечно тоже не идеал (идеал — это если бы данные прямо в стеке были, кстати, большинство реализаций std::string имеют оптимизацию, которая для малых строк размещает их прямо внутри объекта, на стеке), но работает не плохо, т.к. современные процессоры уже научились оптимизировать подобный код.

Для строк это еще более менее применимо. Но хранить вектор в стеке — это как вообще? Как это будет вообще работать в embedded, где размер стека часто бывает сильно ограничен?

Так я про это и говорил, что я этого не знаю (как и вы оказывается), потому что использую другие механизмы для многопоточности и соответственно не проверял сам руками. Но если они это не поправили, то это может оказаться весьма неприятным сюрпризом для многих программистов, уже привыкающих к современному стандарту.

Я подозреваю, если передаются implicit sharing, то разницы вообще не заметят эти самые программисты. Зато дополнительные трахи с rvalue семантикой очень можно приобрести. Меня поэтому это вообще не заботит, я кстати тоже программист С++, который не брезгует новым стандартом.

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

Я не вижу где она кривая в упор. Зато неоднократно видел кривость решений где "Qt только для интерфейсов".

Соответственно лично я хотел бы в идеале иметь чисто GUI библиотеку (а не «фреймворк всего») с возможностями уровня Qt и таким же набором поддерживаемых платформ. При этом написанную в стиле современного C++. Это был бы идеал. Но такого к сожалению не существует, поэтому живём с чем есть — с Qt. )))

Попробуйте openFrameworks. Мне кажется вам понравится.
Для строк это еще более менее применимо. Но хранить вектор в стеке — это как вообще? Как это будет вообще работать в embedded, где размер стека часто бывает сильно ограничен?

Так я и написал, что подобная оптимизация встречается только у std::string и только для коротких строк. Но в любом случае и std::vector и std::string с длинными строками всё равно намного эффективнее реализаций из Qt, в которых получается двойная косвенность.

Я подозреваю, если передаются implicit sharing, то разницы вообще не заметят эти самые программисты. Зато дополнительные трахи с rvalue семантикой очень можно приобрести. Меня поэтому это вообще не заботит, я кстати тоже программист С++, который не брезгует новым стандартом.

Вот как раз повсеместное использование ссылочных типов — это и есть несоответствие стилю современного C++, которые ориентирован скорее на типы-значения.

Попробуйте openFrameworks. Мне кажется вам понравится.

Так там же как раз есть всё, кроме GUI библиотеки. )))
Так я и написал, что подобная оптимизация встречается только у std::string и только для коротких строк. Но в любом случае и std::vector и std::string с длинными строками всё равно намного эффективнее реализаций из Qt, в которых получается двойная косвенность.

Эффективнее тем, что память выделяется в стеке, а не в куче? А есть какие нибудь статьи которые сравнивают производительность хранения в этих областях памяти? И вообще, это так принципиально хранить строки крайне производительно?

Вот как раз повсеместное использование ссылочных типов — это и есть несоответствие стилю современного C++, которые ориентирован скорее на типы-значения.

Просто забудьте как устроен implicit sharing и думайте что в qt move семантика повсеместна. Потому что это практически одно и тоже по производитльности (дайте мне бенчмарки где это опровергается). Я вообще считаю, что move семантика еще один способ выстрелить себе в ногу:
Если говорить, об изменениях, которые имели негативный эффект, то совершенно точно это rvalue ссылки. Они очень мощные, но их очень сложно использовать правильно и слишком легко — неправильно. Я сам научился использовать их строго только в тех случаях, в которых я абсолютно уверен.
— цитата от сюда. Сами создатели стандарта это утверждают.

Так там же как раз есть всё, кроме GUI библиотеки. )))

Есть, свои контролы которые отрисовываются в opengl.
Эффективнее тем, что память выделяется в стеке, а не в куче? А есть какие нибудь статьи которые сравнивают производительность хранения в этих областях памяти? И вообще, это так принципиально хранить строки крайне производительно?

Работа с память в стеке очевидно быстрее (там это всего лишь изменение одного регистра, а не множество разных операций), как раз поэтому и появился например std::array. Но вообще то речь в данной дискуссии была совсем о другом, о чём я уже писал. Ещё раз повторюсь, здесь речь не про SSO в std::string (которое кстати всего лишь частенько встречается в конкретных реализациях, а не является стандартом), а про общее устройство всех контейнеров в стандартной библиотеке языка. И данные там хранятся в обычной куче (ну естественно если мы говорим про стандартные аллокаторы), но при этом там только один уровень косвенности, который современные процессоры нормально оптимизируют.

Просто забудьте как устроен implicit sharing и думайте что в qt move семантика повсеместна. Потому что это практически одно и тоже по производитльности (дайте мне бенчмарки где это опровергается).

Эм, ну естественно, что это одно и тоже, если мы говорим об операции присвоения, которую оптимизируют с помощью этих техник. Но речь то совсем о другом. О том, что сам контейнер начинает работать намного хуже при такой реализации. Потому что если строка вида std_vector[0] превращается компилятором в *(std_vector.data_pointer), то строка вида qvector[0] будет превращаться в *(qvector.shared_pointer->data_pointer), что уже плохо для процессора.

Кстати, в Qt есть один класс ориентированный на эффективность. QVarLengthArray — там не только нет Implicit Sharing, но и вообще есть попытка работы на стеке (пока не потребуется увеличение памяти). Т.е. это такая мутантная смесь std::array и std::vector, впрочем вполне симпатичная. Так вот в документации к ней можно увидеть всю нужную информацию о быстродействие — раз уж нет желания верить моим словам, то предлагаю поверить документации Qt. )))

Есть, свои контролы которые отрисовываются в opengl.

Речь про это http://openframeworks.cc/documentation/ofxGui/? ))) Ну да, почти что уровень Qt или wxWidgets. )))
Наконец то я понял о какой производительности идет речь. А теперь из той же документации:

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.

Во первых, необходимость в этом классе редка и связана она с накладными расходами на выделение динамической памяти, что естественно больше. Я знаю только два случая, когда это становится ощутимой проблемой — в embedded разработке и высоконагруженных приложениях. В первом случае стараются выделить в куче (или в стеке если размер позволяет) память заранее и затем её распределять, например через placement new или просто вручную. Во втором переопределяют операторы new и delete глобально и оптимизируют выделение памяти. Оба этих варианта решают проблему глобально, без допольнительных специальных классов типа std::array или QVarLengthArray. А вот использование стековой памяти неявно где это только возможно чревато проблемами в той же embedded разработке, я полагаю.
Во вторых, исходя из той же документации, это был внутренний класс Qt который они использовали, а значит вполне возможно используется и в контейнерах, и вытащили его на тот случай, если он пригодиться. Мне вот не пригодился ни разу.

std_vector[0] превращается компилятором в *(std_vector.data_pointer), то строка вида qvector[0] будет превращаться в *(qvector.shared_pointer->data_pointer), что уже плохо для процессора.

Нет, у QVector вместо оператора [], нужно вызывать метод at для извлечения элементов, он напрямую обращается к памяти по указателю. Можете посмотреть исходники QVector и проверить. Это идет в разрез с устоявшемся в stl operator [] и методом at, где смысл примерно наоборот этих методов, но контейнеры qt были когда stl еще не был стандартом.
Наконец то я понял о какой производительности идет речь.

Неправильно поняли в который раз — всё время речь шла не про стек, а про обычную кучу. А примеры со стеком были для иллюстрации самой оптимальной работы с памятью, которая однако далеко не всегда доступна.

А теперь из той же документации:

А надо было на другую цитату обратить внимание: "QVector uses implicit sharing as a memory optimization. QVarLengthArray doesn't provide that feature; however, it usually produces slightly better performance due to reduced overhead, especially in tight loops."

Да. и вообще QVarLengthArray в отличие от std::array работает не только с памятью на стеке. Но при этом он будет всё равно эффективнее QVector, как раз из-за отсутствия implicit sharing.

Нет, у QVector вместо оператора [], нужно вызывать метод at для извлечения элементов, он напрямую обращается к памяти по указателю. Можете посмотреть исходники QVector и проверить. Это идет в разрез с устоявшемся в stl operator [] и методом at, где смысл примерно наоборот этих методов, но контейнеры qt были когда stl еще не был стандартом.

Да причём тут вызываемые методы? ) От них ничего не зависит. Речь о том, как хранятся данные. Т.е. указатель на что хранится в объекте. Любой лишний уровень косвенности (а именно его привносит implicit sharing) убивает быстродействие.

Это абсолютно точно так же как и с виртуальными функциями, вызов которых намного медленнее обычных. И в этом смысле Qt как раз идёт по пути Java/C# (где вообще все функции объектов виртуальные от рождения) и в противоречие с тенденциями современного C++, где стараются больше использовать статический полиморфизм.
Для такого кода:

#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;
}

на fedora 23 (gcc 5.3.1) я получил следующие результаты:

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

Но строго говоря все подобные замеры очень приблизительные, т.к. в течение замера выполняющийся процесс может прерываться операционной системой в определнные моменты времени, поэтому было бы интересно посмотреть на реальное исследование данного вопроса, которых я к сожалению до сих пор не встречал (я имею ввиду сравнение производительности qtl и stl).
У меня этот же тест выдает:

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

Qt 5.5, gcc 4.9.2, windows7 64 бит. Можно надо просто правильные ключики передавать компилятору? )

P.S. В современном C++ подобные циклы записываются как for(auto i: stlVector), хотя на быстродействие это естественно никак не влияет. )
Ну так какие ключики? И как у вас gcc 4.9.2 в windows может быть? Может быть имеется ввиду mingw? Да, и вы точно не в дебаг версии смотрели? Откуда такая разница?
Я компилировал так: -std=gnu++14 -march=native -O3.

Да, кстати, но вообще тест выше несколько сомнительный, потому что он описывает слишком идеальную ситуацию, в которой размер данных не меняется. В таком случае собственно и vector не нужен, а достаточно сделать new int[size]. А для измерения реальной работы вектора надо проводить измерения например для push_back. Я хотел попробовать сделать такой пример и тут меня ждали такие шокирующие новости, что на их фоне все наши разговоры о тонких нюансах оптимизации стали смешными. Хотя это на самом деле можно было и раньше заподозрить, просто заметив в документации на QVector, что там size() возвращает int. Но мне как-то оно не бросилось в глаза. Зато сейчас, запустив такой простейший код:

#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;
}

я был шокирован полученным вылетом qt вектора с исключением bad_alloc. Хотя потом естественно всё быстро разъяснилось (кстати, кому-то требуются пояснения?). Но говорить после этого о каком-то качестве контейнеров в Qt просто смешно.

P.S. При компиляции данного примера в 32-ух битный код начнёт вылетать уже и std::vector, но это уже как бы абсолютно понятно и законно. )
Хотя потом естественно всё быстро разъяснилось (кстати, кому-то требуются пояснения?)

А вот интересно было бы узнать. Слишком большой блок памяти выделяли (в сумме на всё про всё четыре гига)?
Слишком большой для int'a (кстати тоже шедевр, даже не unsigned int, а просто int! Вообще жесть была в голове у архитектора этой библиотеки)… Просто это может быть не очевидно, т.к. выделяется вроде как меньше (500'000'000*4), но тут надо учесть, что вектор обычно выделяет память удваивая её, а не увеличивая на размер нового элемента. Так что упираемся в размер int'a уже на 500'000'000 int'ов. )))
А значения то в итоге какие получились? Этот пример тоже не корректен, т.к. у stl вектора и qvector могут быть разные правила увеличения capacity.

Я кстати прогнал тест для цифры 200000000, вот мои результаты:

stl vector push_back: 1089
qt vector push_back: 1043

У QVector есть ограничение на максимальный размер, можно посмотреть например здесь — QTBUG-33019

А если зарезервировать память заранее в 500000000 то bad alloc не возникает. Что в принципе и нужно делать при работе с таким большим количеством данных.
А значения то в итоге какие получились? Этот пример тоже не корректен, т.к. у stl вектора и qvector могут быть разные правила увеличения capacity.

Конечно там могут быть разные алгоритмы и как раз их сравнение тоже будет помогать сравнивать качество кода. Но как видно, до таких тонких нюансов мы так и не добрались — всё стало ясно намного раньше. )))

Я кстати прогнал тест для цифры 200000000, вот мои результаты:

Для 200000000 у меня показывает такое:
stl vector push_back: 884
qt vector push_back: 941

Но это на самом деле вообще не имеет значения на фоне обнаруженного. )))

У QVector есть ограничение на максимальный размер, можно посмотреть например здесь — QTBUG-33019

Это не ограничение, а дикий косяк! Не уметь работать в 2016-ом году с 64 битным кодом? Ужас!

А если зарезервировать память заранее в 500000000 то bad alloc не возникает. Что в принципе и нужно делать при работе с таким большим количеством данных.

Ну для начала я уже говорил, что если выделять память заранее, то собственно и вектор не особо нужен — достаточно сделать new int[size]. ))) Но ладно, предположим что будет хранить в векторе и резервировать заранее… Ну так а что если мы передадим не 500'000'000, а 2'000'000'000? ))) Ну или если пугают такие цифры, то можно взять и те же самые 500'000'000 (или даже меньше), но взять в качестве элемента не int, а какую-нибудь структурку… У std::vector никаких проблем не будет, пока хватает оперативной памяти. )
Речь шла о том, что Qt ни на что не годиться кроме интерфейсов, и поэтому необходимо тянуть кучу сторонних библиотек. stl vector тянуть не надо, он всегда есть. Хотите юзать большие данные как у вас в примере — юзайте его. У меня такой необходимости никогда не было.
Ну про кучу сторонних библиотек я уже говорил, что один Boost (а это стандарт де-факто в мире C++) покрывает 90% задач. Причём с настоящими качеством кода, а не как в Qt.
boost получается тоже нельзя использовать, как вы миритесь с такой багой например?
Но это на самом деле вообще не имеет значения на фоне обнаруженного. )))
Отнюдь. Вы получили сбой в искусственном тесте, только и всего. Вероятность его возникновения в реальных условиях невысока. Хотя мне тоже непонятно, почему решили ввести такое ограничение, это не трагедия.
Спасибо. Теперь мне понятно, почему так, и аргументы Thiago мне кажутся вполне разумными. А вот тот факт, что это не отражено в документации — нет.
Да, и решение, которое по нему фактически приняли, мне не нравится.
Тест то конечно искусственный, но речь то уже не о нём, а о диком факте, который вскрылся на этом тесте. А факт этот заключается в том, что 64-ёx битный QVector не способен выделить более 2ГБ памяти. Это сразу приговор, если говорить о попытке замены им std::vector — уже даже не важны другие мелкие факты, типа тормознутости на некоторых операциях.

Кстати, до появления семантики перемещения в C++, реализация QVector (и других подобных контейнеров) ещё имела какие-то плюсы в определённых сценариях по сравнению с STL. Но теперь там уже нет ни одного плюса, одни минусы. Это даже в том случае, если никогда не понадобится выделить много памяти...
У семантики перемещения меньше кейсов использования, чем у implicit sharing и проблем с кодом от нее, как я уже говорил выше, больше.
Никакой это не приговор. Если известно, что вектор не разрастётся до таких размеров, QVector вполне можно использовать. В противном случае надо хорошенько подумать о замене простым массивом, потому что push — очень дорогая операция: во-первых распределяется второй массив, ещё больше предыдущего (т.е. ещё 2+ гига), во-вторых производится копирование содержимого первого массива во второй.
В противном случае надо хорошенько подумать о замене простым массивом

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

Я как-то писал парсер на плюсах. В нём были деревья. Так вот, я в начале пробовал использовать деревья, которые когда-то писал для задач общего назначения. А потом понял, у меня они имеют весьма ограниченный набор операций. Сделал специализированную структуру данных для них — и получил прирост в быстродействии тридцать процентов и семьдесят по объёму данных.
Потому что если строка вида std_vector[0] превращается компилятором в *(std_vector.data_pointer), то строка вида qvector[0] будет превращаться в *(qvector.shared_pointer->data_pointer), что уже плохо для процессора.

В самом деле нет, в qt данные выделяются одним куском с счетчиком ссылок (shared_pointer и data_pointer), по скорости доступа все точно также как с std::vector. Если и сравнивать qt implicit sharing так с boost::intrusive_ptr.
Есть две категории ПО — "моя программа работает в 10 раз быстрее твоей и имеет в разы более красивый код" и "ага, зато моя работает", и Qt — яркий представитель второй категории.
Я недавно отвязывал движок своего OpenSource-проекта от Qt (по лицензионным причинам, LGPL нравится не всем), и пришлось столкнуться лицом к лицу с "голым" C++, в котором даже директорию на ФС нельзя создать кроссплатформенным образом, не говоря уже о нормальной поддержке UCS2, локализации, дизайнера UI и чертовой прорвы всего остального, ради которого я готов потерпеть MOC, даже если он будет всегда.
Проблема в том, что я до сих ищу подтверждения того, что Qt чем то уступает по скорости каким то другим приложениям.
Одна из немногих старых статей которые я нашел хоть с каким то бенчмарком — http://incise.org/hash-table-benchmarks.html, и что интересно, буст там оказался в жестких аутсайдерах.
Ну или вот еще троллинг от самих троллтехов — http://blog.qt.io/blog/2009/08/24/with-or-without-qt/

Зато видел вот такой удивительный пример, когда чувак отказался от Qt QMutex в пользу стандартного и просел в производительности — http://stackoverflow.com/questions/29170495/performance-of-stdmutex-and-qmutex-in-mingw-64-posix-thread-version

Так что вопрос по быстроте программы для меня остается открытым.
Я не имел в виду только быстроту, скорее упомянутую выше "внутреннюю кривизну архитектуры". Признаюсь честно и публично — мне абсолютно до лампочки внутренняя архитектура Qt при написании GUI-приложений на нем, пока не приходится лезть внутрь библиотеки с молотком и паяльником. Я знаю, что "некрасивый самолёт не может летать", но я бы не назвал ни саму Qt, ни код, который получается писать с её использованием очень уж уродливыми. Да, макросы, да, MOC, да, кое-где можно сделать лучше, только вот альтернатив толковых многим классам QtCore (да тому же, прости рандом, QString) я так и не смог подобрать.
Я согласен. ) Потому и сам использую Qt. Но исключительно для GUI. Для всего остального я что-то не припомню каких-то вещей в Qt, которые были бы принципиально удобнее.
Еще проблема Qt в том, что его компоненты (виджеты, сеть, потоки и т. д.) хотя и разделены между собой, но для коммуникации с ними (компонентами) используется общий сигнал/слотовый механизм. Т.е. если не подключать сторонние библиотеки (asio, tbb и прочее), всё выглядит целостно, в противном случае нужно или делать свои wrapper-ы для коммуникации с ними или оставлять мешанину функций-адаптеров на виду. Было бы неплохо иметь альтернативный архитектурный вариант, тот же паттерн observer или listener.
Я имел ввиду архитектурное решение, отличное от сигналов/слотов, к примеру (пока опустим плюсы и минусы):

class MyListener : public QSpinBoxListener
{ 
public:
  void valueChanged(int value) override { ... }
};
...
MyListener *ln = new MyListener();
QSpinBox *sb = new QSpinBox(ln);
Добавил изменения. Ещё раз спасибо за комментарий!
А зачем нужен FastDelegate? Есть же вполне кошерный std::function, который ещё и компилятор пытается заоптимизировать?
Кстати, если используется clang, то для него есть экспериментальный плагин, который заменяет собой moc и вроде бы умеет шаблоны.
std::function появился в С++11, а я начинал писать некоторые классы ядра библиотеки в незапамятные студенческие времена (как pet project), когда поддержка этого стандарта ещё не была мейнстримом. Потом уже как-то по инерции старался делать всё без зависимостей от С++11 и старше.
Понимаю, сейчас такая идея звучит глупо и когда буду релизить библиотеку, наверно, форкнусь и сделаю её версию с поддержкой С++11… Но пока стараюсь делать под старый стандарт.

П.С.: Кстати, некоторые люди пробовали делать бэнчмарки (не знаю насколько чистый эксперимент был) и утверждают, что FastDelegate работает быстрее чем std::function (статья 2014-ого года).
П.П.С.: Кстати, ещё один довод за FastDelegate — я узнал про него когда работал в геймдевной конторе, где много внимания уделяли оптимизации проекта. Там использовали FastDelegate с самописными улучшениями. Конечно, у них было ещё ограничение на поддержку С++11, но всё же...
std::function может и появился в C++11, а вот boost::function (из которого кстати std:: и был скопирован) уже лет 15 наверное спокойно работает на любых компиляторах. )))
Ну, если говорить про boost::function как про стороннее решение, то, как по мне, лучше иметь зависимость от легковесного FastDelegate, который реализован в трёх файликах исходного кода, чем тащить либу из boost со всеми зависимостями (ссылка на репу Boost.Function, есть кой-какие зависимости, если я не ошибаюсь).
Дело в том, что boost это библиотека по умолчанию для всех C++ проектов, поэтому тащить ничего не приходится, boost это де-факто стандарт в C++ мире, и его неиспользование в проекте может вызвать только вопросы.
Boost у меня обычно подключён раньше Qt, т.к. используется для всех приложений, а не только для GUI. ))) И если говорить про зависимости, то из всего огромного списка библиотек в Boost'e, только их небольшое число требуют компиляции (и соответственно какого-то подключения в виде библиотек потом), а большая часть реализована просто в виде заголовочных файлов. Соответственно "тащить либу со всеми зависимостями" сводится к одному банальному #include. ))) И может применяться даже при программирование микроконтроллеров, у которых по 16КБ памяти на всё приложение. )))
У меня, конечно, не очень большой опыт разработки С++, пока всего работал в двух компаниях — и ни одна не использовала boost (я, во всяком случае, не видел). Возможно, это связано со спецификой областей (первая работа дрова для принтеров, вторая — геймдев), но знакомые в области тоже boost весьма умеренно использовали. Причём из тех мотиваций что я слышал — это как раз громоздкость boost, то, что его нужно почтит целиком тащить если даже одна фича из него нужна. Миф или есть реально удобный способ сепарировать его на самостоятельные кусочки?
К слову, на GitHub есть бенчмарки разных сигнально-слотовых реализаций, возможно что-то заинтересует. Некоторые реализации (к примеру, lsignal) приспособлены к многопоточности.
Sign up to leave a comment.

Articles