はじめに
kerasを使っていて、プログレスバーがかっこいいなと思いました。
pythonではtqdm
というパッケージがあり、簡単に表示できましたが、C++では簡単ではありませんでした(boost/progress.hpp
でできるが)。
また、クラスを作ってみたかったので、経験として自分で作ってみることにしました。
Githubで公開しています。→ https://github.com/ysuzuki19/CppProgressBar
コード全体をここに貼ると長くなる上、今後変更しづらくなってしまうので、コード全体は貼らずに部分だけ紹介していきます。
・ ラムダ式で自分の処理を与える
・ プログレスバーを表示しながら、自分で出力したいものも出力できる
・ ターミナルサイズに合わせてプログレスバー長を変更する(途中で変更したら崩れる)
頑張って調べて作りましたが、自主学習であるため、C++的にマズイ記述がある可能性があり責任は取れません。何かお気づきの方は言及してもらえると助かります!プルリクも歓迎です。
完成品
kerasみたいでカッコイイ😋(真似したんですけどね笑)
C++でこれ出したい人いるはず!使ってみてください!
使い方
クラスを準備したのだが、適宜呼び出して使うのは面倒だと思い、for_progress()
という関数を準備しておいた。呼び出しは以下で行うことができる。
for_progress( loop_number, lambda_func );
// loop_number : ループ回数を指定
// lambda_func : 自分の処理をラムダ式にしたもの
ここで、ラムダ式の引数にstd::string output_string
という出力用の文字列が必要となっている。出力が必要なければ、引数として記述するだけで放置してしまえばよい。また、カウンタによって配列の要素にアクセスしたい場合や処理の結果を保存したい場合にはfor_progress()
を呼び出す前に初期化した変数をラムダ式でキャプチャする必要がある。
使用例
今回は、使用している様子を分かりしやすくするためにあえて遅延させた。また、処理には、「0~100
の数値の和」・「0~10
の数値の積」を準備してそれぞれプログレスバーを出力した。ここで、プログレスバーをみやすくするためにsleep_for()
で遅延させた。
実際に呼び出すために作成したテストコード全文が以下。
# include <iostream>
# include <string>
# include <CppProgressBar.hpp>
# include <chrono>
# include <thread>
using namespace std;
int main()
{
{// for_progress test for sums
int sums = 0;
int cnt = 0;
auto process = [&sums, &cnt](std::string& output_string) {
output_string = to_string(cnt) + " " + to_string(sums);
std::this_thread::sleep_for(std::chrono::milliseconds(30));
sums += cnt;
cnt ++;
};
for_progress(100, process);
}
{// for_progress test for times
double times = 1;
int cnt = 1;
auto process_2 = [×, &cnt](std::string& output_string) {
output_string = to_string(cnt) + " : " + to_string(times);
std::this_thread::sleep_for(std::chrono::milliseconds(30));
times *= cnt;
cnt ++;
};
for_progress(10, process_2);
}
return 0;
}
C++で出力した行を上書きする方法
キャリッジリターンというものを用いる。ここで、関数使用時に出力したい内容がプログレスバーより小さい場合、単純にキャリッジリターンを行うだけでは、プログレスバーの残りカスが画面に残って非常に残念なターミナルになってしまう。そこで考えたのが、
プログレスバーと同じ文字数のスペース埋めされた`std::string`を出力して再びキャリッジリターン
というものである。つまり以下の処理を通常のfor
ループに追加した。
- プログレスバーを出力
- 次のプログレスバーに備えてキャリッジリターン
- 出力が{ある, なし}で分岐
- ある:スペース埋めしてキャリッジリターンして文字出力したのちプログレスバー出力
- ない:プログレスバーを出力
この処理を加える事で、for_progress()
使用時に好みの出力が可能となった。
クラスのメンバ変数の名前の末尾にアンダーバーをつける
Googleのスタイルシートの変数名項に書いてあった。
つける前に自分が犯してしまったミスは、「引数とメンバ変数がごっちゃになった」というものである。
インクルードガード
C++で#include
をする際に、標準ライブラリの場合は複数回同じヘッダをインクルードしようとしてもコンパイルは成功する。それに対し、インクルードガードを書いていないヘッダを複数回インクルードしようとするとコンパイルは失敗する。それを防ぐためにインクルードガードをする必要がある。(#pragma once
はよくわかっていない)
自分の書いたヘッダファイルの最上部・最下部に以下のような記述をするとよい。
# ifndef MY_HEADER_HPP
# define MY_HEADER_HPP
...
# endif
こうすることで、このヘッダは読み込んだな!
とコンパイラが気づいてくれるらしい。複数回同じヘッダをインクルードしてもコンパイルできるようになる。
アクセサ
クラスのメンバ変数はPublic
にしないのが一般的という事だが、安全性・保守性のためらしい。
クラスのメンバ変数に直にアクセスできるというのはコーディングミスの原因になりそうな上、どこでメンバ変数が書き換えられたか分かりづらいという面がある。
なのでPrivate
にして、アクセスしたいループカウンタにはアクセサを準備した。また、どれも1行になったのでinline
にした。
Private:
size_t loop_counter_;
Public:
inline void cntSet(size_t first) {
loop_counter_ = first;
}
inline size_t cntGet() {
return loop_counter_;
}
inline void cntIncrement() {
loop_counter_ ++;
}
-
cntSet()
: ループカウンタに引数の値をセットする -
cntGet()
: ループカウンタの値をゲットする -
cntIncrement()
: ループカウンタの値をインクリメントする
プログレスバーに関するメンバ関数
アクセサ以外の各メンバ関数は以下である。
void init_variable(size_t loop_number);
void update_variable();
inline void display_progress_bar();
inline void finish_progress_bar();
template <typename T> inline void stdout_in_for_progress (T& e);
基本的には関数名から読み取れるとは思うが、各説明は以下。
-
init_variable()
: ループ回数に対して各メンバ変数を初期化 -
update_variable()
: プログレスバー等のメンバ変数を更新する -
display_progress_bar()
: プログレスバーを出力する -
stdout_in_for_progress()
: プログレスバーを表示中に標準出力する。
追加するかもしれない機能
自分が欲しくなるor需要があれば以下の機能を追加しようかと考えている。
- プログレスバーを表示しながら処理時間も表示する
- 途中でターミナルサイズが変更される場合も対応(
ioctl()
を呼び直すだけだと思ったが今回はバグのため断念) - 枠線やカーソルを自由に決められる
- イテレータを引数にできるように
- 二重ループに対応
参考
初めてクラスを作成したので、いろいろな方の情報を参考にさせていただきました。ありがとうございました。
-
https://google.github.io/styleguide/cppguide.html
- Googleの超いいやつ
- 書き方に困ったらとりあえずみると安心する
-
https://ttsuki.github.io/styleguide/cppguide.ja.html#Braced_Initializer_List
- Googleの超いいやつの和訳
-
https://qiita.com/h6akh/items/fc8674bc2251ff0dfbf3
- 最初に目を通した
- どんな風にヘッダを作るのかの参考
-
https://www.learncpp.com/cpp-tutorial/header-guards/
- インクルードガードの参考
-
https://qiita.com/yskszk/items/2045ef74396fbaf18f4d
- メンバ変数の
Private
化とアクセサの準備
- メンバ変数の
-
https://ja.wikipedia.org/wiki/キャリッジ・リターン
- キャリッジリターンとは
-
https://en.cppreference.com/w/c/language/escape
- C++のキャリッジリターン
-
https://stackoverflow.com/questions/1022957/getting-terminal-width-in-c/1022961#1022961
- ターミナルサイズの取得
-
https://ja.cppreference.com/w/cpp/keyword
- C++の予約語の調査
おわりに
はじめてclass・ヘッダを作ったので、学ばなけばいけないことが多く、大変勉強になりました。
最初にも述べましたが、初心者ですので、おかしい点があれば教えていただけたら幸いです。