5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C++でプログレスバー表示する関数を作った[はじめてのヘッダ作り]

Last updated at Posted at 2019-11-09

はじめに

kerasを使っていて、プログレスバーがかっこいいなと思いました。
pythonではtqdmというパッケージがあり、簡単に表示できましたが、C++では簡単ではありませんでした(boost/progress.hppでできるが)。
また、クラスを作ってみたかったので、経験として自分で作ってみることにしました。

Githubで公開しています。→ https://github.com/ysuzuki19/CppProgressBar

コード全体をここに貼ると長くなる上、今後変更しづらくなってしまうので、コード全体は貼らずに部分だけ紹介していきます。

特徴
・ ラムダ式で自分の処理を与える
・ プログレスバーを表示しながら、自分で出力したいものも出力できる
・ ターミナルサイズに合わせてプログレスバー長を変更する(途中で変更したら崩れる)

頑張って調べて作りましたが、自主学習であるため、C++的にマズイ記述がある可能性があり責任は取れません。何かお気づきの方は言及してもらえると助かります!プルリクも歓迎です。

完成品

kerasみたいでカッコイイ😋(真似したんですけどね笑)
C++でこれ出したい人いるはず!使ってみてください!

  1. forループに合わせてプログレスバーを出力する図
    progressbar_nooutput.gif

  2. forループに合わせてプログレスバーを出力しつつ好みの情報を出力する図
    progressbar.gif

使い方

クラスを準備したのだが、適宜呼び出して使うのは面倒だと思い、for_progress()という関数を準備しておいた。呼び出しは以下で行うことができる。

for_progress()の呼び出し方法
for_progress( loop_number, lambda_func );
// loop_number : ループ回数を指定
// lambda_func : 自分の処理をラムダ式にしたもの

ここで、ラムダ式の引数にstd::string output_stringという出力用の文字列が必要となっている。出力が必要なければ、引数として記述するだけで放置してしまえばよい。また、カウンタによって配列の要素にアクセスしたい場合や処理の結果を保存したい場合にはfor_progress()を呼び出す前に初期化した変数をラムダ式でキャプチャする必要がある。

使用例

今回は、使用している様子を分かりしやすくするためにあえて遅延させた。また、処理には、「0~100の数値の和」・「0~10の数値の積」を準備してそれぞれプログレスバーを出力した。ここで、プログレスバーをみやすくするためにsleep_for()で遅延させた。

実際に呼び出すために作成したテストコード全文が以下。

test.cpp
# 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 = [&times, &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++で出力した行を上書きする方法

キャリッジリターンというものを用いる。ここで、関数使用時に出力したい内容がプログレスバーより小さい場合、単純にキャリッジリターンを行うだけでは、プログレスバーの残りカスが画面に残って非常に残念なターミナルになってしまう。そこで考えたのが、

overwrite_progressbar
プログレスバーと同じ文字数のスペース埋めされた`std::string`を出力して再びキャリッジリターン

というものである。つまり以下の処理を通常のforループに追加した。

  • プログレスバーを出力
  • 次のプログレスバーに備えてキャリッジリターン
  • 出力が{ある, なし}で分岐
    • ある:スペース埋めしてキャリッジリターンして文字出力したのちプログレスバー出力
    • ない:プログレスバーを出力

この処理を加える事で、for_progress()使用時に好みの出力が可能となった。

クラスのメンバ変数の名前の末尾にアンダーバーをつける

Googleのスタイルシートの変数名項に書いてあった。
つける前に自分が犯してしまったミスは、「引数とメンバ変数がごっちゃになった」というものである。

インクルードガード

C++で#includeをする際に、標準ライブラリの場合は複数回同じヘッダをインクルードしようとしてもコンパイルは成功する。それに対し、インクルードガードを書いていないヘッダを複数回インクルードしようとするとコンパイルは失敗する。それを防ぐためにインクルードガードをする必要がある。(#pragma onceはよくわかっていない)

自分の書いたヘッダファイルの最上部・最下部に以下のような記述をするとよい。

my_header.hpp
# ifndef MY_HEADER_HPP
# define MY_HEADER_HPP
...
# endif

こうすることで、このヘッダは読み込んだな!とコンパイラが気づいてくれるらしい。複数回同じヘッダをインクルードしてもコンパイルできるようになる。

アクセサ

クラスのメンバ変数はPublicにしないのが一般的という事だが、安全性・保守性のためらしい。
クラスのメンバ変数に直にアクセスできるというのはコーディングミスの原因になりそうな上、どこでメンバ変数が書き換えられたか分かりづらいという面がある。
なのでPrivateにして、アクセスしたいループカウンタにはアクセサを準備した。また、どれも1行になったのでinlineにした。

accessor
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() : ループカウンタの値をインクリメントする

プログレスバーに関するメンバ関数

アクセサ以外の各メンバ関数は以下である。

member_functions
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()を呼び直すだけだと思ったが今回はバグのため断念)
  • 枠線やカーソルを自由に決められる
  • イテレータを引数にできるように
  • 二重ループに対応

参考

初めてクラスを作成したので、いろいろな方の情報を参考にさせていただきました。ありがとうございました。

おわりに

はじめてclass・ヘッダを作ったので、学ばなけばいけないことが多く、大変勉強になりました。
最初にも述べましたが、初心者ですので、おかしい点があれば教えていただけたら幸いです。

5
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?