Qt デフォルトで使用可能な組み込みアイコン QStyle::StandardPixmaps
Qtにはデフォルトで組み込まれているアイコンがいくつかあり、使用出来るアイコンの種類についてはQStyleのQStyle::StandardPixmapsというenumに列挙されています。
このenumをQStyleのstandardIconまたはstandardPixmap関数の引数に渡してやれば指定した種類のアイコンがQIconやQPixmap形式で取得することができます。
取得できるアイコンはその関数を呼ぶQStyleインスタンスのStyleの種類によって変わりますが、そのアプリケーションのデフォルトのものを使いたければ以下のようにQApplicationから取得するのが一番手っ取り早い方法だと思います。
QIcon icon = QApplication::style()->standardIcon( QStyle::SP_TitleBarMenuButton );
この方法で自分の環境のQt4.8.3 Macのデフォルト状態で取得したアイコン一覧を載せておきます。
これだけあればちょっとしたアプリならわざわざリソースに追加しなくても済む場合も多いかとおもいます。
さすがにMac版ではQStyle::SP_VistaShieldは空みたいですね。
Qt ソートON状態のアイテムビュークラスへのアイテムの追加の際の実行コスト
QtにはQAbstractItemViewを継承したアイテムビュークラスがいくつかデフォルトで用意されていますが、この内のQListWidget,QTableView,QTreeViewとこれらを継承したクラス(QTableWidget, QTreeWidget)にはアイテムを自動でソートして表示してくれる機能があり、setSortingEnabled関数でソート機能のON/OFFを切り替えられます。
このソート機能ですが、クラスによってはこれがONの状態でアイテムが編集された場合その都度ソートが実行されるようでOFFの場合と比べると実行コストの差があるようです。
そこでQListWidget,QTableWidget,QTreeWidgetの3つのクラスに連続にアイテムを追加していった場合のソートON/OFFによる影響を計測してみました。
以下がそのコードとその出力です。
#include <QApplication> #include <QListWidget> #include <QTableWidget> #include <QTreeWidget> #include <QDebug> #include <QTime> #include <QStringList> #include <QVector> int main(int argc, char *argv[]) { QApplication a(argc, argv); // アイテム作成用の文字列のリストを作成 // アイテムは "0000", "0001", "0002", ....... "9999" までの10000個 const int N = 10000; QVector<QString> item_strings; item_strings.reserve( N ); for( int i = 0; i < N; ++i ) { item_strings.push_back( QString::number( i ).rightJustified( 4, '0' ) ); } { // QListWidget QListWidget *list_widget = new QListWidget(); { QTime t; t.start(); foreach( const QString &item_string, item_strings ) { list_widget->addItem( item_string ); } qDebug() << "QListWidget sorting OFF :" << t.elapsed() << "ms"; } // clear & sorting ON list_widget->clear(); list_widget->setSortingEnabled( true ); { QTime t; t.start(); foreach( const QString &item_string, item_strings ) { list_widget->addItem( item_string ); } qDebug() << "QListWidget sorting ON :" << t.elapsed() << "ms"; } } { // QTableWidget QTableWidget *table_widget = new QTableWidget(); table_widget->setColumnCount( 1 ); table_widget->setRowCount( N ); { QTime t; t.start(); for( int i = 0; i < N; ++i ) { table_widget->setItem( i, 0, new QTableWidgetItem( item_strings[i] ) ); } qDebug() << "QTableWidget sorting OFF :" << t.elapsed() << "ms"; } // clear & sorting ON table_widget->clear(); table_widget->setColumnCount( 1 ); table_widget->setRowCount( N ); table_widget->setSortingEnabled( true ); { QTime t; t.start(); for( int i = 0; i < N; ++i ) { table_widget->setItem( i, 0, new QTableWidgetItem( item_strings[i] ) ); } qDebug() << "QTableWidget sorting ON :" << t.elapsed() << "ms"; } } { // QTreeWidget QTreeWidget *tree_widget = new QTreeWidget(); { QTime t; t.start(); for( int i = 0; i < N; ++i ) { QTreeWidgetItem *item = new QTreeWidgetItem( QStringList() << item_strings[i] ); tree_widget->addTopLevelItem( item ); } qDebug() << "QTreeWidget sorting OFF :" << t.elapsed() << "ms"; } // clear & sorting ON tree_widget->clear(); tree_widget->setSortingEnabled( true ); { QTime t; t.start(); for( int i = 0; i < N; ++i ) { QTreeWidgetItem *item = new QTreeWidgetItem( QStringList() << item_strings[i] ); tree_widget->addTopLevelItem( item ); } qDebug() << "QTreeWidget sorting ON :" << t.elapsed() << "ms"; } } return 0; }
出力
QListWidget sorting OFF : 250 ms QListWidget sorting ON : 491 ms QTableWidget sorting OFF : 12 ms QTableWidget sorting ON : 13 ms QTreeWidget sorting OFF : 350 ms QTreeWidget sorting ON : 352 ms
結果としてQTableWidgetとQTreeWidgetはソートON/OFFの差はみられないようですが、QListWidgetの場合ソートON時はOFFに比べて2倍近く実行コストがかかっていることがわかります。
QListWidgetを使う場合で大量のアイテムを一気に追加する際などは一度ソート機能をOFFにしておいた方がパフォーマンスが少しあがりそうです。
ちなみに今回の検証に用いた環境はMac版のQt4.8.3です。その他の環境ではまた違った結果となる可能性がありますのでその点はご了承ください。
参考
QAbstractItemView Class Reference | Documentation | Qt Project
Qt 意外と使えるQTabBar
QTabWidgetのタブの部分だけのウィジェットとしてQTabBarというクラスがあります
このQTabBarは意外と実用的なクラスですがQt Designerで追加できるウィジェットには含まれていません。
そのため知名度は低いように思ったので紹介してみようと思います。
このウィジェットもQWidgetを継承しているクラスのため、その他のウィジェットと同じように他のウィジェット上に配置することができます。
とりあえずウィジェットの真ん中に配置すると見た目はこのような感じになります。
タブの操作はQTabWidgetと同じように、
追加はaddTab関数やinsertTab関数で末尾に追加や途中への追加、
削除はremoveTabでインデックスを指定して削除、
というように操作可能です。
もちろんcurrentChangedシグナルも実装されているためカレントタブが変更されたことも通知してくれます。
またsetTabButtonという関数では、例えば"閉じるボタン"などのボタンをタブ上に追加が可能です。
ボタンの配置はLeftSideとRightSideを選ぶことができます。
ですが実はこの関数、TabButtonという関数名ですが引数で受けつけるのはQWidgetのポインタなので、
QWidgetを継承したウィジェットならばどんなウィジェットでも配置可能なのです。
下は使いどころがあるかは不明ですが、タブにQComboBoxとQCheckBoxを追加したサンプルです。
このように利用用途がありそうなQTabBarですが、やはりQt Designerでformを作成している場合、Qt Designer上のフォームエディタでQTabBarも配置したいと考えるかと思います。
その場合、以下の方法で若干無理やりではありますがフォームエディタ上でQTabBarも配置できました。
下のようなフォームでButton1とButton2の間に配置したいとします。
そこにQWidgetをリストからドラッグして追加します。
追加したQWidgetを右クリックして"格上げ先を指定"を選択します。
ここで立ち上がったダイアログで
ベースクラス名: QWidget
格上げされたクラス名: QTabBar
ヘッダファイル: QTabBar
と入力し、"追加"、"格上げ"ボタンを順に押します。
ソースの方でタブをいくつか追加してみて実行します。
これでフォームエディタからQTabBarを配置できました。
もし上記の方法で駄目だった場合、例えば下のようなQTabBarを継承しただけのカスタムクラスを作成し、そのクラスへウィジェットの格上げを行うことで
同じようにフォームエディタでの配置が可能だと思います。
mytabbar.h
#ifndef _MYTABBAR_H_ #define _MYTABBAR_H_ #include <QTabBar> class MyTabBar : public QTabBar { public: explicit CustomTabBar( QWidget *parent = 0 ) : QTabBar( parent ) {} }; #endif // _MYTABBAR_H_
Qt QGraphicsItem でHard-Edgeなギザギザ曲線を描画する
QtのQGraphicsEllipseなどのアイテムを描画すると、曲線部分でもどれだけ拡大していっても輪郭はなめらかに補間されたベクタ形式として表示されますが、これをラスタ形式のように輪郭がHard-Edgeでギザギザな輪郭をもったアイテムとして表示する方法を以下に紹介します。(かなり無理やりな方法です)
まず通常ならQGraphicsEllipseを使用する楕円描画を例に、ギザギザな輪郭の楕円描画を目標として説明します。
先に簡単に方法を説明すると
楕円の形状をQRegionに入れる ↓ QRegionからQPainterPathに変換 ↓ QPainterPathをQGraphicsPainterItemにセットして表示
という流れになります。
サンプルコードは以下です。
// 楕円に内接する四角形(x, y, width, height) const QRect ellipse( 10, 10, 40, 20 ); // QRegionへ変換 const QRegion ellipse_region( ellipse, QRegion::Ellipse ); // QPainterPathへ追加 QPainterPath path; path.addRegion( ellipse_region ); path = path.simplified(); // QGraphicsPathItem作成 QGraphicsPathItem * item = new QGraphicsPathItem( path, 0, scene );
このようにすると通常だと下のようになめらかな曲線のアイテムを
下のようにギザギザな輪郭曲線で描画できます。
上記の画像のQGraphicsViewは400%x400%で拡大しています。
また上では説明していない
path = path.simplified();
の部分ですが、この行をコメントアウトすると下のようになります。
これはQRegionに楕円を入れた時点でQRegionの内部ではただの矩形の集合として保持されるため、その矩形の輪郭がすべて描画されるためです。これを避けるためにQPainterPathのsimplified()という関数を使用しています。この関数は、上の結果をみれば分かりますが、内部のQPainterPath内部で隣接や領域が重複している部分をすべて結合したコピーを返す関数です。
今回の方法はQRegion内部では図形を単なる矩形の集合として表現していることを利用して、一旦QRegionに変換することで輪郭がギザギザな図形描画を実現しています。
なので単純な楕円以外の複雑な図形でもQRegionに変換しさえすれば同じ方法が使えるはずです。
参考
QRegion Class Reference | Documentation | Qt Project
QPainterPath Class Reference | Documentation | Qt Project
Qt QColor で使用できるカラーネーム
QColorではのコンストラクタやsetNamedColor関数で"#RRGGBB"等の形式の文字列によって色を指定することができます。
ですがこういった16進数を文字列で表現した形式ではなく"blue"や"limegreen"などのようにカラーネームを使用することもできます。
使用できるカラーネームはQColor::colorNames()で一覧を取得できますが、公式ドキュメントによるとはWorld Wide Web Consortium (W3C)によって勧告されているSVG color keyword namesに含まれるものは全て使用できるようです。
以下のページでその一覧と見本、それぞれのRGB値を見ることができます。
Basic Data Types and Interfaces – SVG 1.1 (Second Edition)
文字列がQColorに変換可能かを調べるときはQColor::isValidColor関数で変換の可否をboolで取得できます。
またQColorのインスタンスからname()で色の文字列を取得できますが、取得できる文字列はカラーネームで指定した場合でも"#RRGGBB"の形式でしか取得することができません。
#include <QDebug> #include <QColor> int main() { qDebug() << "blue == #0000ff :" << ( QColor( "blue" ) == QColor( "#0000ff" ) ); QColor blue_color; blue_color.setNamedColor( "#00f" ); qDebug() << "blue == #00f :" << ( QColor( "blue" ) == blue_color ); qDebug() << "is limegreen valid? :" << QColor::isValidColor( "limegreen" ); qDebug() << "is hoge valid? :" << QColor::isValidColor( "hoge" ); qDebug() << "QColor( \"limegreen\" ).name() :" << QColor( "limegreen" ).name(); return 0; }
出力
blue == #0000ff : true blue == #00f : true is limegreen valid? : true is hoge valid? : false QColor( "limegreen" ).name() : "#32cd32"
C++のコードをブラウザ実行可能なJavaScriptに変換するEmscripten導入メモ
C/C++などllvm/clangでコンパイル可能なコードをブラウザで実行可能なJavaScriptに変換してくれるコンパイラツールであるEmscriptenの導入を行った際のメモです。
環境はOS X 10.8 Mountain LionのMacBook Air (11-inch and 13-inch, Mid 2011)です。
Emscripten本体以外の依存パッケージはすべてHomebrewからインストールしました。
Homebrewの導入がまだの人は下のサイトなどを参考にしてHomebrewとgitをインストールしましょう。
Mac/OS X/Homebrew - PukiWiki
MacPortsより使いやすい!?パッケージ管理システムHomebrewの使い方 | Macとかの雑記帳
Emscripten本体とその他 依存パッケージのインストール
Emscriptenの初期設定
とりあえずダウンロードしてきたemscriptenディレクトリのなかのem++実行してみます。
すると自分の環境では
env: python2: No such file or directory
というエラーが出ました。
em++のshebangを覗いてみると
#!/usr/bin/env python2
となっており/usr/binに実行可能なpython2がなかったことが原因らしいので
sudo ln -s /usr/bin/python2.7 /usr/bin/python2
でpyshon2.7のシンボリックリンクを作成しました。
では気をとり直してem++をもう一度実行します。
すると
============================================================================== Welcome to Emscripten! This is the first time any of the Emscripten tools has been run. A settings file has been copied to ~/.emscripten, at absolute path: /Users/nukesaq88/.emscripten It contains our best guesses for the important paths, which are: LLVM_ROOT = /usr/bin PYTHON = /usr/bin/python NODE_JS = /usr/local/bin/node EMSCRIPTEN_ROOT = /Users/nukesaq88/work/opt/emscripten Please edit the file if any of those are incorrect. This command will now exit. When you are done editing those paths, re-run it. ==============================================================================
というメッセージが出てhomeディレクトリに設定ファイルの.emscriptenを作成したので設定の確認をしろ と促されます。これは初回実行時のみの表示です。
なので~/.emscriptenを開いて設定を編集します。
自分の場合デフォルトのままだと Homebrewでインストールしたllvmが使用されるように、LLVM_ROOTのパスの部分を'/usr/bin'から'/usr/local/bin'に変更しただけです。
これで初期設定は終わりです。
次は実際にC++のコードをJavaScriptに変換してみます。
C++からJavaScriptへの変換テスト
とりあえずテスト用のC++コードを作成します。
例: hello_world.cpp
#include <cstdio> int main() { std::printf("Hello World!\n"); return 0; }
このコードをem++を以下のように実行して変換したJavaScriptを含んだhtmlファイルを作成します。
./emscripten/em++ -o hello_world.html ./hello_world.cpp
このコマンドで作成されたhello_world.htmlをブラウザで開くと
こんな感じで問題なく変換されていることが確認できました。
今回はこれで終了です。
これからいろいろと触っていきたいと思います。
参考にさせていただいたサイト
Tutorial · kripken/emscripten Wiki · GitHub
How to get Emscripten running on OS X.
Emscripten入門(導入編) - ushiroad
emscriptenでC++からJavaScriptへ変換しよう
Safx: Emscriptenを用いてC++ソースをJavaScriptに変換する
homebrewのパッケージをWebで検索できるサービスbraumeister.org
Mac OS X用のパッケージ管理システムとしてHomebrewがありますが、このHomebrewのパッケージ検索をWebからできるサービスとしてbraumeister.orgというものがあります。
braumeister.org
パッケージの検索は通常コマンドラインから行いますが、このサービスを利用すればHomebrew導入前のMacや、Mac以外の環境からもパッケージの検索が行えます。
さらにパッケージの最新のバージョンや、そのパッケージの依存関係なども辿っていくことが可能です。
ちなみに通常方法でパッケージの検索する場合コマンドラインから以下のように検索します。
# すべて表示 brew search # "hoge"を検索 brew search hoge
Qt QLabelでHTMLハイパーリンクの外部リンクを有効にする
QLabelではHTML形式で表示するテキストを指定することができますが、
デフォルトではハイパーリンクで外部リンクを開く機能がオフになっています。
これを有効にするには以下のようにsetOpenExternalLinks関数で設定を変更してやる必要があります。
qLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse); qLabel->setOpenExternalLinks(true); qLabel->setTextFormat(Qt::RichText); qLabel->setText("<a href=¥"http://nukesaq88.hatenablog.com/¥">unstable diary</a>");
setOpenExternalLinksという名前の関数はQLabel以外でもQGraphicsTextItem, QTextBrowserにもあるようなので、これらのクラスでも同様に外部リンクの設定を変えられるようです。
参考
c++ - Making QLabel behave like a hyperlink - Stack Overflow
月の杜工房 - Qt 定番小ワザ集
C++ 双方向マップ boost::bimaps 覚え書き
双方向のからアクセス可能なmapとしてboost::bimapsがあります。
今回はそのboost::bimapsの覚え書きです。
まずは単純な1対1の双方向mapとして使う場合
#include <string> #include <iostream> #include <boost/bimap/bimap.hpp> int main() { // int : string の1対1のmap typedef boost::bimaps::bimap<int,std::string> bimap_t; typedef bimap_t::value_type bimap_value_t; bimap_t bm; // 要素の挿入 bm.insert( bimap_value_t( 1, "one" ) ); bm.insert( bimap_value_t( 2, "two" ) ); bm.insert( bimap_value_t( 3, "three" ) ); bm.insert( bimap_value_t( 4, "four" ) ); // サイズを確認 std::cout << "size : " << bm.size() << std::endl; // [output] size: 4 // 左側をkey,右側をvalueとして参照 std::cout << "left.at(2) : " << bm.left.at( 2 ) << std::endl; // [output] left.at(2) : two // 右側をkey,左側をvalueとして参照 std::cout << "right.at(¥"three¥") : " << bm.right.at( "three" ) << std::endl; // [output] right.at("three") : 3 // 要素の無いkeyを参照した場合 std::out_of_range例外が発生する try { bm.left.at(5); } catch( const std::out_of_range &e ) { std::cout << "Out of range exception occured. CAUSE = " << e.what() << std::endl; } // 要素があるかどうかはstd::mapと同様にfind(key)の戻り値のイテレータをend()との比較することで確認できる std::cout << std::boolalpha; std::cout << "left contains 4 : " << ( bm.left.find(4) != bm.left.end() ) << std::endl; // [output] left contains 4 : true std::cout << "left contains 5 : " << ( bm.left.find(5) != bm.left.end() ) << std::endl; // [output] left contains 5 : false // 既に同じ要素がある場合、挿入しても何も変化しない(更新されない) bm.insert( bimap_value_t( 2, "2" ) ); bm.insert( bimap_value_t( 5, "three" ) ); // 前と同じ結果 std::cout << "size : " << bm.size() << std::endl; // [output] size: 4 std::cout << "left.at(2) : " << bm.left.at( 2 ) << std::endl; // [output] left.at(2) : two std::cout << "right.at(¥"three¥") : " << bm.right.at( "three" ) << std::endl; // [output] right.at("three") : 3 std::cout << "left contains 5 : " << ( bm.left.find(5) != bm.left.end() ) << std::endl; // [output] left contains 5 : false // 要素の置き換え { // 成功パターン: (1, "one") -> (1, "ONE") へ置き換え bimap_t::right_iterator itr = bm.right.find( bm.left.at( 1 ) ); const bool successful_replace = bm.right.replace_key( itr, "ONE" ); std::cout << "replace (1, ¥"one¥") -> (1, ¥"ONE¥") successful : " << successful_replace << std::endl; std::cout << "left.at(1) : " << bm.left.at( 1 ) << std::endl; // [output] left.at(1) : ONE } { // 失敗パターン: (1, "ONE") -> (1, "two") へ置き換え bimap_t::right_iterator itr = bm.right.find( bm.left.at( 1 ) ); const bool successful_replace = bm.right.replace_key( itr, "two" ); std::cout << "replace (1, ¥"ONE¥") -> (1, ¥"two¥") successful : " << successful_replace << std::endl; std::cout << "left.at(1) : " << bm.left.at( 1 ) << std::endl; // [output] left.at(1) : ONE } return 0; }
実行結果
size : 4 left.at(2) : two right.at("three") : 3 Out of range exception occured. CAUSE = bimap<>: invalid key left contains 4 : true left contains 5 : false size : 4 left.at(2) : two right.at("three") : 3 left contains 5 : false replace (1, "one") -> (1, "ONE") successful : true left.at(1) : ONE replace (1, "ONE") -> (1, "two") successful : false left.at(1) : ONE
多対1の双方向mapとして使う場合
#include <string> #include <iostream> #include <boost/bimap/bimap.hpp> #include <boost/bimap/multiset_of.hpp> #include <boost/foreach.hpp> #include <boost/range.hpp> int main() { // int : string の多対1のmap typedef boost::bimaps::multiset_of<int> int_set_t; typedef boost::bimaps::bimap<int_set_t,std::string> bimap_t; typedef bimap_t::value_type bimap_value_t; bimap_t bm; // 要素の挿入 bm.insert( bimap_value_t( 1, "one" ) ); bm.insert( bimap_value_t( 2, "two" ) ); bm.insert( bimap_value_t( 3, "three" ) ); bm.insert( bimap_value_t( 4, "four" ) ); bm.insert( bimap_value_t( 1, "uno" ) ); bm.insert( bimap_value_t( 2, "due" ) ); bm.insert( bimap_value_t( 3, "tre" ) ); bm.insert( bimap_value_t( 4, "quattro" ) ); // サイズを確認 std::cout << "size : " << bm.size() << std::endl; // [output] size: 8 // 右側をkeyとしてならat(key)で参照できる std::cout << "right.at(¥"three¥") : " << bm.right.at( "three" ) << std::endl; // [output] right.at("three") : 3 std::cout << "right.at(¥"tre¥") : " << bm.right.at( "tre" ) << std::endl; // [output] right.at("tre") : 3 // 左側をkeyとしては参照できない // bm.left.at( 2 ); // Compile Error!! // 左側の要素が2のものをカウント std::cout << "left.equal_range(2) count : " << boost::distance( bm.left.equal_range(2) ) << std::endl; // [output] left.equal_range(2) count : 2 // 左側の要素が2のものを全て参照する { { // BOOST_FOREACHを使う場合 std::cout << "left.equal_range(2) with BOOST_FOREACH :" << std::endl; BOOST_FOREACH( bimap_t::left_const_reference x, bm.left.equal_range( 2 ) ) { std::cout << " " << x.second << std::endl; } } { // BOOST_FOREACHを使わない場合 std::cout << "left.equal_range(2) without BOOST_FOREACH :" << std::endl; typedef bimap_t::left_const_iterator l_itr_t; typedef std::pair<l_itr_t,l_itr_t> l_itr_range_t; const l_itr_range_t &range = bm.left.equal_range( 2 ); const l_itr_t end_itr = range.second; for( l_itr_t itr = range.first; itr != end_itr; ++itr ) { bimap_t::left_const_reference x = *itr; std::cout << " " << x.second << std::endl; } } } return 0; }
実行結果
size : 8 right.at("three") : 3 right.at("tre") : 3 left.equal_range(2) count : 2 left.equal_range(2) with BOOST_FOREACH : two due left.equal_range(2) without BOOST_FOREACH : two due
要素に情報を付加する
要素の挿入時にあとで参照可能な付加情報をつけることができます。
#include <string> #include <iostream> #include <boost/bimap/bimap.hpp> #include <boost/bimap/multiset_of.hpp> #include <boost/foreach.hpp> int main() { // int : string の多対1のmapにstringでinfoを付加 typedef boost::bimaps::multiset_of<int> int_set_t; typedef boost::bimaps::with_info<std::string> language_info_t; typedef boost::bimaps::bimap<int_set_t,std::string,language_info_t> bimap_t; typedef bimap_t::value_type bimap_value_t; const std::string LANG_ENGLISH( "English" ); const std::string LANG_ITALIAN( "Italian" ); const std::string LANG_JAPANESE( "Japanese" ); bimap_t bm; // 要素の挿入 bm.insert( bimap_value_t( 1, "one", LANG_ENGLISH ) ); bm.insert( bimap_value_t( 2, "two", LANG_ENGLISH ) ); bm.insert( bimap_value_t( 3, "three", LANG_ENGLISH ) ); bm.insert( bimap_value_t( 4, "four", LANG_ENGLISH ) ); bm.insert( bimap_value_t( 1, "uno", LANG_ITALIAN ) ); bm.insert( bimap_value_t( 2, "due", LANG_ITALIAN ) ); bm.insert( bimap_value_t( 3, "tre", LANG_ITALIAN ) ); bm.insert( bimap_value_t( 4, "quattro", LANG_ITALIAN ) ); bm.insert( bimap_value_t( 1, "ichi", LANG_JAPANESE ) ); bm.insert( bimap_value_t( 2, "ni", LANG_JAPANESE ) ); bm.insert( bimap_value_t( 3, "san", LANG_JAPANESE ) ); bm.insert( bimap_value_t( 4, "yon", LANG_JAPANESE ) ); // サイズを確認 std::cout << "size : " << bm.size() << std::endl; // [output] size: 8 // 右側をkeyにしてその要素のinfoを参照 std::cout << "lang of ¥"quattro¥" : " << bm.right.info_at("quattro") << std::endl; // 左側の要素が2のものを全て参照してinfoも参照する std::cout << "left.equal_range(2) :" << std::endl; BOOST_FOREACH( bimap_t::left_const_reference x, bm.left.equal_range( 2 ) ) { std::cout << " value : " << x.second << ", lang : " << x.info << std::endl; } return 0; }
実行結果
size : 12 lang of "quattro" : Italian left.equal_range(2) : value : two, lang : English value : due, lang : Italian value : ni, lang : Japanese
要素の型をtag付きの型にする
要素の参照等でleft, right などで指定するのではなくtagによって指定できるようになる。
#include <string> #include <iostream> #include <boost/bimap/bimap.hpp> // tag struct number_tag{}; struct string_tag{}; struct language_tag{}; int main() { typedef boost::bimaps::tagged<int,number_tag> tagged_number_t; typedef boost::bimaps::tagged<std::string,string_tag> tagged_string_t; typedef boost::bimaps::tagged<std::string,language_tag> tagged_language_t; typedef boost::bimaps::with_info<tagged_language_t> taggged_language_info_t; // int : string の1対1でstringの付加情報有りのtag付きmap typedef boost::bimaps::bimap<tagged_number_t,tagged_string_t,taggged_language_info_t> bimap_t; typedef bimap_t::value_type bimap_value_t; const std::string LANG_ENGLISH( "English" ); const std::string LANG_ITALIAN( "Italian" ); const std::string LANG_JAPANESE( "Japanese" ); bimap_t bm; // 要素の挿入 bm.insert( bimap_value_t( 1, "one", LANG_ENGLISH ) ); bm.insert( bimap_value_t( 2, "due", LANG_ITALIAN ) ); bm.insert( bimap_value_t( 3, "san", LANG_JAPANESE ) ); // サイズを確認 std::cout << "size : " << bm.size() << std::endl; // [output] size: 4 // tagで指定して参照 std::cout << "by<number_tag>().at(2) : " << bm.by<number_tag>().at(2) << std::endl; // [output] by<number_tag>().at(2) : due std::cout << "by<string_tag>().at(¥"one¥") : " << bm.by<string_tag>().at( "one" ) << std::endl; // [output] by<string_tag>().at("one") : 1 { // イテレータからもtagで要素を参照できる const bimap_t::map_by<number_tag>::const_iterator itr = bm.by<number_tag>().find(3); std::cout << "number : " << itr->get<number_tag>() << ", " << "string : " << itr->get<string_tag>() << ", " << "langage : " << itr->get<language_tag>() << std::endl; } return 0; }
実行結果
size : 3 by<number_tag>().at(2) : due by<string_tag>().at("one") : 1 number : 3, string : san, langage : Japanese
参考
Chapter 1. Boost.Bimap - 1.53.0
letsboost::bimaps
boost::bimapsのメモ - NO!と言えるようになりたい
C++ イテレータインクリメント時の前値、後置の違い
C++ではforループなどでイテレータのインクリメントをする際、前値形式(例: ++itr)と後置形式(例: itr++)のどちらの方法でインクリメントしても計算結果は同じになりますが、多くの場合 わずかですが前値形式(++itr)の方が後置形式(itr++)より高速に動作します。
理由はイテレータがclassとして定義されている場合、後置形式では
内部でインクリメント前のイテレータ一時オブジェクトとしてをコピーしておき
インクリメント後にコピーしておいた一時オブジェクトを返す
というような実装になっていることが多いからです。
int型やポインタ型などの組み込み型の場合、インクリメントの際の前値と後置にパフォーマンスの差は無いはずですが、C++では特に理由が無い場合インクリメントには前値形式を採用する習慣をつけることをおすすめします。
例としていろいろなコンテナのイテレータclassの後置インクリメントの実装を見てみましょう。
VC10 std::vector
_Myiter operator++(int) { // postincrement _Myiter _Tmp = *this; ++*this; return (_Tmp); }
VC10 std::list (上のvectorと全く同じ)
// VC10 std::list iterator _Myiter operator++(int) { // postincrement _Myiter _Tmp = *this; ++*this; return (_Tmp); }
GCC 4.2.1 std::list
_Self operator++(int) { Self __tmp = *this; _M_node = _M_node->_M_next; return __tmp; }
boost1.53.0 boost::container::vector
vector_iterator operator++(int) { pointer tmp = this->m_ptr; ++*this; return vector_iterator(tmp); }
Qt 4.8 QList
inline iterator operator++(int) { Node *n = i; ++i; return n; }
このようにいろいろな実装で同じように一時オブジェクトを作成しているのがわかります。
では実際にどれほどの差があるでしょうか。
以下の方法でベンチマークをとってみました。
1000000個の要素を持ったコンテナを作成し、すべてをfor文でまわしたときの経過時間を前置形式と後置形式でそれぞれ計測。その計測を100回行ったときの平均値を計算する。
その結果は
std::vectorでは 前置形式 6086[μs] 後置形式 8411[μs]
std::listでは 前置形式 6383[μs] 後置形式 9247[μs]
となりました。
どちらも1000000回実行して2〜3[ms]と、ほんのわずかではありますが実際にパフォーマンスに差があることがわかりました。
計測に使用したPCは
MacBook Air (11-inch and 13-inch, Mid 2011)
1.6 GHz Core i5
4 GB 1333MHz DDR3
コンパイラはclang 3.1
計測には以下のコードを用いました。
#include <iostream> #include <chrono> #include <vector> #include <list> int main() { std::vector<int> container; const int NUM = 1000000; for( int i = 0; i < NUM; ++i ) { container.push_back(i); } const auto end_itr = container.end(); { const auto start = std::chrono::system_clock::now(); for( auto itr = container.begin(); itr != end_itr; ++itr ) { /* do nothing */ } const auto stop = std::chrono::system_clock::now(); const auto duration = std::chrono::duration_cast<std::chrono::microseconds>( stop - start ); std::cout << "++itr : " << duration.count() << " us" << std::endl; } { const auto start = std::chrono::system_clock::now(); for( auto itr = container.begin(); itr != end_itr; itr++ ) { /* do nothing */ } const auto stop = std::chrono::system_clock::now(); const auto duration = std::chrono::duration_cast<std::chrono::microseconds>( stop - start ); std::cout << "itr++ : " << duration.count() << " us" << std::endl; } return 0; }
参考
あんちょこ:前置インクリメントと後置インクリメントの違い
ひらせチャンネル - [C++] イテレータにご注意!
std::chronoによる処理時間測定 - fjnlの生存記録のような何か