2013年12月23日月曜日

C++erがQtを使うべき10の理由


この記事は、C++ (fork) Advent Calendar 2013 23日目の記事です

ということで、かなり大きくぶちあげましたが、
つまるところ、世のc++erの皆さんに、Qtを布教しようという魂胆の記事です。
過度な期待はしないで下さい、
当方の環境はubuntu13、10 コンパイラはgccです、他の環境では確認していません。

まず開発環境の準備

このサイトから、自分の環境にあったインストーラをダウンロードしてインストールしてください。
ubuntuなら、
sudo apt-get install qtcreator
でもインストールできます、ただしバージョンがちょっと古いです。

プロジェクトの作成

QtCreatorがインストールできたら、プロジェクトを作成してみましょう。

ファイル→ファイル/プロジェクトの新規作成


アプリケーションからQtコンソールアプリケーションを選択して、プロジェクトを作成してください。
えっ、どうしてQtなのにGUIじゃないのかって、最初っからGUIじゃ面白くないでしょう。



C++11の有効化

メジャーなC++コンパイラは、標準ではC++03モードで動作します、
C++11を使うためには、何らかの方法でコンパイラにオプションを与えなければいけません。
Qtの場合、プロジェクト作成時に生成される、proファイルに、
QMAKE_CXXFLAGS += -std=c++11
と書き加える必要が有ります。
QtCreatorの入力補完はC++11なので、C++11を有効にしないとビルド時にストレスがマッハです。

とりあえずプロジェクト作成完了

作成したプロジェクトには、Makefileを生成する為のproファイルと、main.cppが生成されます。
main.cppは以下のような感じです。

main.cpp

#include <QCoreApplication>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    return a.exec();
}




別にこのままでもいいですが、QCoreApplication a(argc, argv);は今は使わないので、
消し去っても結構です、
main.cpp

#include <QCoreApplication>
int main(int argc, char *argv[])
{
    //QCoreApplication a(argc, argv);

    return 0;//a.exec();
}



C++erがQtを使うべき10の理由 その1

QVectorが速い、すこぶる速い。
以下実証

ソースコード

#include <QCoreApplication>
#include <QVector>
#include <vector>
#include <QDebug>


//rdtscを取得
inline unsigned long long rdtsc()
{
    unsigned int a,b;

    asm("rdtsc":"=a"(a),"=d"(b));

    unsigned long long out=b;
    out=out<<32;
    out+=a;
    return out;
}

int main(int argc, char *argv[])
{
    //QVectorとstd::vectorの速度比較
    //片方をコメントアウト
    std::vector<int>  array{0,9,8,4,9,2,5,7,44,0,3,363,6,9}, array2;
    //QVector<int>  array{0,9,8,4,9,2,5,7,44,0,3,363,6,9}, array2;
    auto time=rdtsc();
    array2=array;
    time=rdtsc()-time;
    qDebug()  << time << "cycle" << (array[0]) << (array2[0]);

    return 0;
}



実行


実行サイクル
Qvector 342 cycle
std::vector 12635 cycle

こんな感じでQVectorの方が、 std::vectorより2桁程度速くなります。
何故速いのかといえば、
QVectorはコピーオンライトで実装されているので。
配列に変更がない限り、実質的には参照渡しだからです。

つまり

こうするとQVectorは遅くなります、


int main(int argc, char *argv[])
{
    //QVectorとstd::vectorの速度比較
    //片方をコメントアウト
    //std::vector<int>  array{0,9,8,4,9,2,5,7,44,0,3,363,6,9}, array2;
    QVector<int>  array{0,9,8,4,9,2,5,7,44,0,3,363,6,9}, array2;
    auto time=rdtsc();
    array2=array;
    //複製した配列を書き換える
    array2[0]=9;
    time=rdtsc()-time;
    qDebug()  << time << "cycle" << (array[0]) << (array2[0]);
    return 0;
}


実行



実行サイクル
Qvector 15219 cycle
std::vector 12416 cycle

実際に複製が行われるとstd::vectorより若干遅くなってしまいます。
ですがQVectorには、処理速度の為に関数の引数に参照を使う必要が無くなるという利点があります。
知らず知らずの内に、配列の複製コストによって処理速度が遅くなってしまう事を回避出来る訳です。

C++erがQtを使うべき10の理由 その2

QDebugが便利
前回の記事でも普通に使っていたQDebugですが、これがどえりゃあ便利な関数である訳です。

QDebugはstd::coutやstd::cerrと違い、自動でスペースと改行が挿入されます


#include <QDebug>
#include <iostream>

int main(int argc, char *argv[])
{
    //要素毎にスペースが 行の終わりには自動で改行が追加される
    qDebug() << "apple" << 100 << "yen";
    std::cout << "apple" << 100 << "yen";
    return 0;
}





出力


apple 100 yen
apple100yen //改行が入らない


C言語のprintfに慣れた方に、こんな使い方も出来ます


#include <QDebug>
#include <iostream>
#include <cstdio>
#include <QString>


int main(int argc, char *argv[])
{
    int  fare=100;
    double  distance=1.50;
    //printfのように書ける
    qDebug("図書館までバスで%ld円 直線距離は%fkm",fare,distance);
    //QStringを使えば 型の指定も要らない 記述が長ったらしくなるのはご愛嬌
    qDebug() << QString("図書館までバスで%1円 直線距離は%2km").arg(fare).arg(distance);
    printf("図書館までバスで%ld円 直線距離は%fkm\n",fare,distance);

    return 0;
}



出力


図書館までバスで100円 直線距離は1.500000km
"図書館までバスで100円 直線距離は1.5km" //引用符が付く
図書館までバスで100円 直線距離は1.500000km//改行が入らない


配列の中身を表示したいときどうしますか、


#include <QVector>
#include <vector>
#include <QDebug>
#include <iostream>

int main(int argc, char *argv[])
{
    //配列を表示したい
    //ビルド出来ない
    //std::vector<int> aaa{0,9,10,5,8};
    //std::cerr << aaa;
    //ビルド出来る
    QVector<int> aaa{0,9,10,5,8};
    qDebug() << aaa;
    return 0;
}



出力


QVector(0, 9, 10, 5, 8)


C++erがQtを使うべき10の理由 その3

至れり尽くせりのコンテナクラス
Qtには、既に紹介したQVector以外にも数多くのコンテナクラスが存在します。特に便利なのはこの二つ

QVector
Qt版の動的配列。std::vecto同様全ての要素が連続したメモリ空間に確保される。

QList
Qt版の双方向連結リスト、std::listと違ってランダムアクセスが可能な優れもの。
QVectorと同じくコピーオンライトで実装されている。要素数が1000以内なら、
先頭追加、挿入、終端追加、すべて高速、ランダムアクセスも速いチートクラス、
だがメモリー消費量だけは勘弁な、Qt推奨


まず検索



#include <QVector>
#include <QDebug>
#include <QList>

int main(int argc, char *argv[])
{
    //配列の中身を検索したいけどどうしよう?
    std::vector<int> array{0,1,2,3,4,5,6,7,8,9};

    QVector<int> vector;
    QList<int> list;

    for(auto elem:array){
        vector.append(elem);
        list.append(elem);
    }

    //QVectorなら検索できるよ
    //一致する要素がない場合 -1 要素がある場合 要素のインデックスが返される
    qDebug() << vector.indexOf(10);//-1
    qDebug() << vector.indexOf(5);//5
    //QListでも検索できるよ
    qDebug() << list.indexOf(10);//-1
    qDebug() << list.indexOf(5);//5

    //indexOf() 先頭要素から検索
    //一致する要素が複数ある場合 最も先頭から近い要素のインデクスが返される
    //第二引数には検索開始位置が指定できる
    qDebug() << list.indexOf(5,6);//-1
    //終端から検索したい場合 lastIndexOf()

    return 0;
}



要素の一部を切り出す



#include <QVector>
#include <vector>
#include <QDebug>
#include <QList>

int main(int argc, char *argv[])
{
    //配列の一部を取り出して使いたい
    std::vector<int> array{0,1,2,3,4,5,6,7,8,9};

    QVector<int> vector;
    QList<int> list;

    for(auto elem:array){
        vector.append(elem);
        list.append(elem);
    }

    //QVectorなら
    //第一引数で切り出し位置 第二引数で切り出し長さ
    auto midvector=vector.mid(3,4);
    qDebug() << midvector;//3, 4, 5, 6
    //QListでも
    auto midlist=list.mid(2,7);
    qDebug() << midlist;//2, 3, 4, 5, 6, 7, 8
    //第二引数は省略できる
    auto midvector2=vector.mid(3);
    qDebug() << midvector2;//3, 4, 5, 6, 7, 8, 9
    //QListでも
    auto midlist2=list.mid(2);
    qDebug() << midlist2;//2, 3, 4, 5, 6, 7, 8, 9

  
  return 0;
}



ソート


#include <QVector>
#include <vector>
#include <QDebug>
#include <QList>

int main(int argc, char *argv[])
{
    //配列をソートしたい
    std::vector<int> array{9,8,7,6,5,4,3,2,1,0};
    QVector<int> vector;
    QList<int> list;
    for(auto elem:array){
        vector.append(elem);
        list.append(elem);
    }
    QVector<int> vector2=vector;
    QList<int> list2=list;

    //標準ライブラリのstd::sortで出来る
    std::sort(vector.begin(),vector.end());
    qDebug() << vector;
    std::sort(list.begin(),list.end());
    qDebug() << list;

    //ダメ絶対
    if(false==true){
        //イテレータの型が同じなら 他の配列のイテレータを引数にしても
        //コンパイルエラーにならない
        //ある配列の先頭から もうひとつの配列の終端までの要素が ソートされる
        //未定義の悪魔召喚せり
        std::sort(vector.begin(),vector2.end());
    }

    //qSortなら安心
    qSort(vector2);
    qDebug() << vector2;
    qSort(list2);
    qDebug() << list2;

    //逆順ソートがしたいなら イテレータが必要になる
    qSort(vector2.begin(),vector2.end(),qGreater<int>());
    qDebug() << vector2;
    qSort(list2.begin(),list2.end(),qGreater<int>());
    qDebug() << list2;

    return 0;
}



QStringList QListで連結されたQString。


#include <QVector>
#include <QDebug>
#include <QString>
#include <QStringList>

int main(int argc, char *argv[])
{
    //QStringListはQStringを格納したQListをラッパーしたクラス
    QStringList strlist={"クリスマス","終了の","お知らせ"};
    qDebug() << strlist;
    //"クリスマス"と言う文字列を格納したQStringは存在する
    qDebug() << strlist.indexOf("クリスマス");//0
    //"終了"という文字列を格納したQStringは存在しない
    qDebug() << strlist.indexOf("終了");//-1
    //一つの文字列に結合
    QString str=strlist.join("");//セパレータがいる
    qDebug() << str;
    //QStringListの中から 任意の文字列が含まれる文を検索したい
    auto indexOf=[](QStringList strlist,QString key){
        int index=0;
        //範囲forが使える
        for(auto &elem:strlist){
            //QStringはQStringで検索できる
            const int exists=elem.indexOf(key);
            //-1ではないなら ヒット
            if(exists>=0){
                return index;
            }
            index++;
        }
        //見つからない
        return -1;
    };

    auto index=indexOf(strlist,"終了");
    qDebug() << strlist[index];

    return 0;
}



QVectorとQListは、かなり似ていますが、一部のメンバー関数の名前が違います 。
詳しくはクラスのリファレンスを確認してください
QVector
Qlist


C++erがQtを使うべき10の理由 その4

ジェネリックなデータベースアクセス
QtSqlではプラットフォームに依存しないデータベースへのアクセスが実装されています。

QtSqlを使う場合、proファイルにある
QT       += core
という記述に、sqlという文字を追加して下さい




#include <QDebug>
#include <QString>
#include <QStringList>
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>
#include <QVariant>

int main(int argc, char *argv[])
{
    //データベースにもアクセス出来る

    //データベースの種類と接続名を設定
    //接続名を設定しないと 二つ以上のデータベースを開いた時 うまく動かない
    auto db=QSqlDatabase::addDatabase("QSQLITE","sqlc");
    //データベースのパスを設定 今回はメモリー上に生成
    db.setDatabaseName(":memory:");
    db.setHostName("yasuki");
    db.setUserName("kudan");
    if(db.open()){
        //データベースを開く
        qDebug() << "データベース オープン" << db.connectionName();
        auto query=QSqlQuery(db);

        //テーブル作成
        if(query.exec("CREATE TABLE test (id integer, name text)")){
            qDebug() << "テーブルの作成 成功";
            //データの挿入
            auto addData=[&](int id,QString name){
                //構文を設定
                query.prepare("INSERT INTO test (id,name) ""VALUES (:id,:name)");
                //値を設定
                query.bindValue(":id",id);
                query.bindValue(":name",name);
                //キュー実行
                return query.exec();
            };

            //データ追加
            addData(0,"kudan");
            addData(1,"yasuki");
            addData(2,"advent");
            addData(3,"c++");
            addData(4,"Qt");

            //データの取り出し
            auto readData=[&](QString where){
                QStringList strlist;
                if(query.exec("SELECT * FROM test "+where)){
                    //クエリの読み出し位置の初期化
                    for(query.first();;){
                        //データの読み出し
                        // valueの戻り値はQVariant 適切な型に変換する
                        int id=query.value(0).toInt();
                        QString name=query.value(1).toString();
                        //QStringListに追加
                        strlist.append(QString("id=%1 name=%2").arg(id).arg(name));
                        if(query.next()==false){
                            //次の要素が存在しない場合 終了
                            break;
                        }
                    }
                }
                //データを返す
                return strlist;
            };

            //idがゼロのデータを検索
            qDebug() << readData(" where id=0 ");
            //nameがadventのデータを検索
            qDebug() << readData(" where name='advent' ");
            //idが1 と3以上の4以下のデータを検索
            qDebug() << readData(" where id=1 or id>=3 and id<=4");

        }
    }

    return 0;
}



C++erがQtを使うべき10の理由 その5

  コマンドラインだけど、たまにはGUI使っても問題ないよね。


#include <QApplication>
#include <QDebug>
#include <QString>
#include <QFile>
#include <QTextStream>
#include <QFileDialog>


int main(int argc, char *argv[])
{
    //QWidgetが使いたいなら必須
    QApplication a(argc, argv);

    //テキストファイルを開きたい
    QFile file;

    //コマンドラインでファイルダイアログが呼べる。
    auto filename=QFileDialog::getOpenFileName();
    file.setFileName(filename);
    if(file.open(QIODevice::ReadOnly)){//読み込み専用
        QTextStream textstream(&file);
        auto data=textstream.readAll();
        qDebug() << data;
    }
    return 0;
}



こんなかんじで、開発中のロジックに、実ファイルを読ませるのが簡単にできます。
コマンドラインアプリケーションでGUIを呼び出すには
proファイルにある
QT       += core
という記述に、guiとwidgetsという文字を追加する必要があります。
またmain関数でのQApplicationの宣言も必須です。

C++erがQtを使うべき10の理由 その6

SIGNAL SLOTによってオブジェクト間の粗結合が実現出来る。
そろそろQtらしい話をしましょう。
QOBjectにはSIGNAL SLOTというメンバー関数があり、 connect関数を使う事で、二つを接続できます。

connect()に渡す引数

    QObject::connect(signalを呼び出すオブジェクトのポインタ,
                     SIGNAL(signal指定したメンバー関数()),
                     signalを受け取るオブジェクトのポインタ,
                     SLOT(slot指定したメンバー関数));





サンプルコード
動作 起動時からの時間を秒単位で数え上げる。

countobject.cpp

#ifndef COUNTOBJECT_H
#define COUNTOBJECT_H
#include <QObject>
#include <QTimer>

//自作のQQbject派生クラス 1秒毎にカウントを増加させる
class CountObject: public QObject
{
    //必須

    Q_OBJECT

public:
    //コンストラクタ
    CountObject(QObject *parent) :
        //QQbjectのコンストラクタを呼び出す
        QObject(parent)
    {
        //タイマーを生成
        timer=new QTimer(this);
        //タイムアウトが発生すると On_Timer()が呼ばれる
        connect(timer,SIGNAL(timeout()),this,SLOT(On_Timer()));
        //QTimerの設定時間はミリ秒
        timer->start(1000);
    }
signals:
    //カウントが変化したら シグナルを発する
    void CountorChange(int count);
private slots:
    //タイマーから呼び出された時
    void On_Timer()
    {
        //桁溢れのチェック
        if(countor>=INT_MAX){
            countor=0;
        }
         countor++;
        //シグナル呼び出し
        CountorChange(countor);
    }

private:
    QTimer *timer;
    int countor=0;
};

#endif // COUNTOBJECT_H


main.cpp

#include <QApplication>
#include <QWidget>
#include <QLCDNumber>
#include <QVBoxLayout>
#include "countobject.h"

int main(int argc, char *argv[])
{
    QApplication a(argc,argv);

    //ウィジェットの生成
    QWidget Widget;
    //デイスプレイの生成
    auto Lcd=new QLCDNumber(&Widget);
    //カウンターを生成
    auto countor=new CountObject(&Widget);
    //シグナルとスロットを接続する
    QObject::connect(countor,SIGNAL(CountorChange(int)),Lcd,SLOT(display(int)));

    //レイアウトの設定
    auto VLayout=new QVBoxLayout(&Widget);
    VLayout->addWidget(Lcd);
    //サイズ設定
    Widget.resize(150,50);
    //表示
    Widget.show();

    return a.exec();
}


testQt5.pro

#-------------------------------------------------
#
# Project created by QtCreator 2013-12-21T17:05:06
#
#-------------------------------------------------

QT       += core gui widgets
QMAKE_CXXFLAGS += -std=c++1y

TARGET = testQt5
#CONFIG   += console
CONFIG   -= app_bundle

TEMPLATE = app


SOURCES += main.cpp

HEADERS += \
    countobject.h


スクリーンショット

こんなかんじに。
SIGNAL SLOTを使う事で、時間をカウントする処理と、秒数を表示する処理を分離出来ます。

C++erがQtを使うべき10の理由 その7

マルチスレッド対応
C++11からは規格でマルチスレッドがサポートされるようになりましたが。
QtのQTheadを使ってマルチスレッド処理を行うこともできます。
QObjectのmoveToThread(&QThead)関数で、オブジェクトの処理を行うスレッドを移動できます。

QThead

さっきのサンプルコードのmain関数を以下の様に書き換えることで、
カウンタ―を別スレッドで実行する事が出来ます。

main.cpp

#include <QThread>
#include <QLCDNumber>
#include <QVBoxLayout>
#include "countobject.h"

int main(int argc, char *argv[])
{
    QApplication a(argc,argv);

    //ウィジェットの生成
    QWidget Widget;
    //デイスプレイの生成
    auto Lcd=new QLCDNumber(&Widget);
    //カウンターを生成
    auto countor=new CountObject(&Widget);
    //まさかのマルチスレッド化
    QThread thread;
    //親ウィジェットをリセット
    countor->setParent(nullptr);
    //QThreadを親に指定
    countor->moveToThread(&thread);
    //シグナルとスロットを接続する
    QObject::connect(countor,SIGNAL(CountorChange(int)),Lcd,SLOT(display(int)));
    //スレッド実行 コメントアウトするとカウントが動かない
    thread.start();
    //レイアウトの設定
    auto VLayout=new QVBoxLayout(&Widget);
    VLayout->addWidget(Lcd);
    //サイズ設定
    Widget.resize(150,50);
    //表示
    Widget.show();

    return a.exec();
}


C++erがQtを使うべき10の理由 その8

マルチメディア対応
Qt Multimedia moduleを使用すると、動画や音楽の再生が簡単に出来ます。

サンプルコード
動作、起動後ファイルダイアログを起動し、選択されたメデイアファイルを再生

main.cpp

#include <QApplication>
#include <QFileDialog>
#include <QMediaPlayer>
#include <QString>
#include <QVideoWidget>
#include <QUrl>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //動画を表示するウィジェットを生成
    QVideoWidget widget;
    //メディアを再生するクラスを生成
    auto mediaplayer=new QMediaPlayer(&widget);
    //再生クラスに出力先としてQVideoWidgetを設定
    mediaplayer->setVideoOutput(&widget);
    widget.resize(640,360);
    //表示
    widget.show();
    //メデイアファイルのパスを取得
    QString filename=QFileDialog::getOpenFileName(&widget,"メディアファイル","","*.mp4 *.mp3");
    if(filename.isEmpty()==false){
        //ファイルネーム設定
        mediaplayer->setMedia(QUrl::fromLocalFile(filename));
        mediaplayer->setVolume(100);
        //再生
        mediaplayer->play();
    }

    return a.exec();
}


testQt5_2.pro

#-------------------------------------------------
#
# Project created by QtCreator 2013-12-22T20:51:29
#
#-------------------------------------------------

QT       += core gui widgets multimedia  multimediawidgets
QMAKE_CXXFLAGS += -std=c++11

TARGET = testQt5_2
CONFIG   += console
CONFIG   -= app_bundle

TEMPLATE = app


SOURCES += main.cpp


C++erがQtを使うべき10の理由 その9

大抵何でもあるよ
qtwidgets

C++erがQtを使うべき10の理由 その10

Qt5.2、android ios正式対応
デスクトップからモバイルまで同じソースコードを共有できます。

カウントウィジェットのスクリーンショット、Qt5.1
Qt5.2でスクリーンショットを取ろうとも思ったのですが。力尽きました。



その他、Qt5では、QpenGLのバージョンの差異やOpenGL ESとの差異を吸収出来たりするらしいのですが。
余り調べてません。

最後に

C++erがQtを使うときに注意すべきこと。

Qtのqmakeは、コードの記述を元にコンパイラに渡すソースコードを生成しています。
これを忘れると、文法上は何の問題もないのにコンパイルが通らないという事が起こります。
例えばconnect関数の実体はqmakeが自動的に生成しています。
もし、新たにオブジェクトを定義したり、connect関数を追加した後にコンパイルが通らなくなったら。
手動でqmakeをしてみましょう。



ということで、C++erがQtを使うべき10の理由いかがだったでしょうか。
まぁ。誰がどう見ても、
タイトル先行の行き当りばったりだということが。
ひしひしと伝わってくる記事になってしまった訳ですが、

Qtを使う一番の利点は、やはり機能が豊富だということです、
複数の環境向けにビルドしたい場合、開発環境も複数作る必要があるわけですが、
QtとC++11対応のコンパイラが有れば、大抵のものが揃ってしまうので、
環境構築の手間が省けるわけです。





0 件のコメント:

コメントを投稿