【C++再履修】参照型のメンバ変数の初期化

参照型の復習

int a1 = 10;
int a2 = 20;

int &b = a1;
// b はa1への参照
// 「別名」と考えるとよいかも
printf("%d %d %d\n", a1, a2, b);  // 10 20 10

b = a2;
// これは、
// bの参照先=a1の中身を書き換えることになる。
// 参照先をa2に変えるわけではない。
// 参照先は初期化時にのみ設定できてその後変更することはできない。
printf("%d %d %d\n", a1, a2, b);  // 20 20 20

// つまり、
int &c;
// これはエラーになる。必ず宣言時に初期化しないといけない。

参照型のメンバ変数の初期化

メンバ変数の場合は「コンストラクタのメンバ初期化子リスト」で初期化する。
コンストラクタ内で代入するのはコンパイルエラーになる。(これは初期化ではない)

class C {
public:
  C(int &v1) : m_a1(v1) {}
//C(int &v1) { m_a1 = v1; }  // こちらはエラー
private:
  int &m_a1;
}


ちなみにコンストラクタの引数の型を誤って int v1 としてしまうと、特にエラーはないが問題が起きるので注意が必要。

class C {
public:
  C(int v1) : m_a1(v1) { printf("%d\n", m_a1); }
  void PrintA1() { printf("%d\n", m_a1); }
private:
  int &m_a1;
}

// ---
int a1 = 10;
C test(a1);  // ここでは正しく10と出る
test.PrintA1(); // ここでは不定の壊れた数値が出る(処理系による)

コンストラクタで受け取った v1 は参照ではなくスタックにコピーされた実体なので、メンバ変数 m_a1 はスタック領域のどこかを指すことになり、コンストラクタを抜けた時点でその領域は破棄され、不正に場所を指してしまう。


実際のところ上記のようなint型でコードを見てもピンとこないかも。
このコードを書こうと思ったきっかけは、以下のように処理の入力となるvectorをconstの参照で受け取ってクラス内に保持しておきたいなぁと思ったのでした。

class C {
public:
  C(const std::vector<int> &v1) : m_a1(v1) {}
private:
  const std::vector<int> &m_a1;
}


…ところでメンバ変数に「m_」とかってもう今どきあまりつけないのでしょうか。

【C++】配列とコンテナ

まえおき

10年ぶりくらいにC++を書いてみると、当たり前だけど分からないことだらけ。
これは、単に忘れていることもあるが、結構新しい機能が追加されたというのもある。私が知っているのは1998年の最初のバージョン。その後2011年に大きなバージョンアップがあり、14年、17年と改定。今後も20、23と予定されている。
とりあえずゆっくりかみしめるように勉強していきます。そんな備忘録。

配列

(※以下C言語的な素の配列を単に「配列」、std::vector等を「コンテナ」と呼びます)


C++11でstd::arrayという固定長配列のコンテナが追加され、(コンテナとしての)配列を使う必要性はほとんどなくなった?


std::beginとかstd::endとかはC++11から素の配列にも使え、いわゆるrange based forも配列にも使える。
そしてC++17で、std::sizeにより配列長も分かるようになった。(とはいえこれは以前からテンプレート関数を用いて配列長を取得するテクニックが公式化されたという感じ)

とはいえ、関数の引数でポインタで渡された配列なんかは、この新しいstd:sizeをもってしてもサイズを知ることはできない。


ということで、配列でも比較的安全に(今風に)書くことはある程度できそう。
使いどころによってはstd::arrayより見た目はすっきりしそう。
どっちを使うかっていうのはローカルなコーディングルールを作るんだろうなぁ。

…とりあえずこれからの自分的な方針としては、
できるだけstd::arrayで書いてみることにします。
そのうえで明らかに配列のほうがよさそうな場面を発見できるかどうか。

コード例

// C時代のやりかた
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
// C++時代(C++17より前)のやりかた
template<typename T, std::size_t S> std::size_t array_size(const T(&)[S]) { return S; }

//----------------------------------------------

int a1[] = {0,1,2,3,4};

// C時代のsizeof
for (std::size_t i = 0; i < ARRAY_SIZE(a1); i++) { }
// C++時代のテンプレート関数
for (std::size_t i = 0; i < array_size(a1); i++) { }
// C++17のstd::size
for (std::size_t i = 0; i < std::size(a1); i++) { }

// 関数の引数の場合
ArrayFuncTest(a1);

//----------------------------------------------
// ArrayFuncTest の実装例
//----------------------------------------------
// これだとaはポインタになるのでテンプレート展開に失敗する
void ArrayFuncTest(int a[]) {  std::size_t s = std::size(a); }

// これに限った場合、配列サイズは5なのでこう書くとうまくいく
// (&a)と書かないといけないのはそうしないとポインタと認識されるため
void ArrayFuncTest(int (&a)[5]) {  std::size_t s = std::size(a); }

// ↑これを汎用的にするにはテンプレート関数にすればできますが、、、これは上記「array_size」と同じことで、今回やりたかったこととは少し違う。
template<std::size_t SIZE>
void ArrayFuncTest(int (&a)[SIZE]) { std::size_t s = SIZE; }

// じゃあこれは?
void ArrayFuncTest(int (&a)[]) {  std::size_t s = std::size(a); }
// 引数の型変換にも失敗するし、sizeのテンプレート展開にも失敗する
// error C2664: 'void ofApp::ArrayTestPrint(int (&)[])': 引数 1 を 'int [5]' から 'int (&)[]' へ変換できません。
// error C2672: 'std::size': 一致するオーバーロードされた関数が見つかりませんでした。
// error C2893: 関数テンプレート 'unknown-type std::size(const _Container &)' の特定に失敗しました


ちなみにサイズの型について、

// 昔の人はこう書きがちです。
for (int i = 0; i < std::size(a1); i++) { }
// sizeの戻り値はsize_tなのでint型のiとの比較でワーニングが出る。

// そんな時こそauto!?
for (auto i = 0; i < std::size(a1); i++) { }
// あれ、ダメなのか・・・この場合はautoはやっぱりintになる。

// こうしたらうまく推論できそうだけど・・。
for (auto i = 0, s = std::size(a1); i < s; i++) { }
// コンパイルエラー:
// error C3538: 宣言子リストの中で、'auto' の推論結果は常に同じ型でなければなりません。

残念です。


おまけ:噂のrange based forも配列に使えます。

// range based for
for (int n : a1) { }
// 便利やー。
// ただこれだと値をコピーしているのが気になるので、参照のほうがいいのかな。
for (int& n : a1) { }

参考

C++で静的配列の要素数を求めるテンプレート関数 | TECHSCORE BLOG
テンプレート関数による配列サイズの取得。
実は知らなかった。


生配列よりもstd::arrayを使った方が良い理由 - ぷろみん
配列とarrayの比較。ただこの記事の段階ではまだstd::sizeがない時代。


配列引数の要素数を調べる - meryngii.neta
配列の引数に(&)を付けないといけない部分の考察

openFrameworks導入

はじめに

C++でちょっとした図形を書きながら処理を作りたい案件があり、openFrameworksを導入してみた。

あわよくばiOSとかHTML5とかでも動いたらいいなぁとか思って、いろいろ調べてSDLやcocos2d-xも候補に挙がったが、いや、一歩目はもっとさっくりとにかく素のC++で、と思い直し、openFrameworksにたどり着きました。メディアアーティスト感あるし。

環境構築

https://qiita.com/Gaccho/items/f193d1c350b45b93f663
これに従えば、ほぼその通り導入できた。

異なった点:
新規プロジェクトを作る際に、プロジェクトのテンプレート選択で、「インストール済み>Visual C++>テスト」の下にopenFrameworksが入ってた。

ビルド時のワーニング

コンパイル時にコードページのワーニングC4819が大量に出る。
なんか昔からこういうのは尽きないよね…。
回避方法は… ↓警告無視オプションを設定するって、なんか他にないの??
「プロジェクトのプロパティ>構成プロパティ>C/C++>詳細設定>指定の警告を無効にする」で4819と指定する。

ちょっと悪あがきしてみる。
ofVec2f.hとofVec3f.hは、実際にどこかにasciiじゃない文字コードが入ってるみたいで、VSCodeで開くとWindows1252と認識されたので、BOM付UTF-8にしたらワーニング取れた。
freetype.hはVSCodeでもUTF-8だし、ワーニング出ている行も空行(というか「*」しかない行)だったりしてよくわからないけど、同じくBOM付UTF-8にしたらワーニング取れた・・。うーむスペースとか改行とか目視不能なところになにか紛れているのだろうか。バイナリエディタで見るとかまではしません…。

あんまりライブラリのソースを変更するのは嫌だけど今回はこれでいいや。

直近で必要な処理を調査

とりあえずラインとか矩形とかの描画と、テキスト描画、キーボード・マウス入力当たりがあればよい。

簡単な図形描画

f:id:nakadaieng:20190821153507p:plain
太線はイメージが違う・・まあそうなる理由はわかる。
なんで円が多角形なんだろうか。
直線がぼやけて見えるのはよくある0.5ピクセル問題?

文字

ofDrawBitmapStringでASCII文字は出せる。いったんこれで充分。
f:id:nakadaieng:20190821153658p:plain

ofDrawBitmapString("scale:" + std::to_string(r), ofsx, ofsy);

「std::to_string」というものを知る・・。
C++で文字列を扱う最近のトレンドは何なんだろうか。
(最近のC++の再学習に関してはまた別途やる必要がある)

printfはコンソールに出る。
TTFのフォントを使うモジュールもあるらしいが今は不要。

入力

自動生成されたofAppに、すでに、
key~、Mouse~という、入力イベントを受け取る関数ができている。

void ofApp::keyPressed(int key)
{
    printf("press %d\n", key);
}

 
keyはASCIIコードが入ってくるとのこと。
ちなみに、Enterは13(いわゆるCR)、Shiftは1と3680が来た。まあいいやその辺は使うまい。

なりゆきでエンジン修正してプルリクしてみた その2:プルリクする【UE4 / Unreal Engine 4】

前回の続き。

せっかくエンジンのバグを直したのでプルリクしてみます! 

その2:プルリクする

alweiさんの手順を参考にする。
GitHubのアカウント登録や、Epicのアカウントとの紐づけの説明はここでは省略します。

Gitクライアント

自分が使ったことがあるという理由だけですが、今回はSourceTreeを使うことにしました。あんまりSourceTreeでUE4のプルリクっていう記事もなさそうだし。
SourceTree自体の説明はしません。

GitHub push用のメアドの設定(GitHub 及び SourceTree)

GitHubにcommit~pushする際に、ユーザー名とメールアドレスを登録する必要があるが、この文字列はそのまま履歴に記録されるので、公にしたくない場合はpushするためだけのダミーメールアドレスを発行する設定がある。GitHubで発行したダミーメールアドレスでcommitした履歴は自動的にそのGitHubアカウントに紐づく。

https://qiita.com/sta/items/982ab68e8220a81d485c
こちらの記事のとおりです。
 
SourceTree側では、「ツール>オプション」
「全般>デフォルトユーザー情報」の「フルネーム」と「メールアドレス」
の部分です。
  • 「メールアドレス」にGitHubで取得したメアドを設定します。
  • 「フルネーム」もログに記載されるので公開されても問題のない文字列にしておきます。

Fork(GitHub

これは冒頭のalweiさんの記事のとおりで、GitHub上のForkボタンを押すだけです。

Clone(SourceTree)

「ファイル>新規/クローンを作成する」

まずGitHubのアカウントを登録する。

  • 「Remote」から、「アカウントを追加」
  • GitHubを選択して、
  • 「OAuthトークンを再読み込み」を押す。
  • ブラウザでページが開く
  • 下部「Authorize atlassian」を押し、パスワードを入力する。
  • SourceTreeのアカウント設定ダイアログに戻って「認証に成功」と出たらOK

    f:id:nakadaieng:20190716190601p:plain

続いてCloneする。

  • 「Remote」タブのまま、先ほど追加したアカウントを選択すると、リポジトリ一覧が出る。
  • 一覧からForkしたリポジトリを選択し、「Clone」を押す。EpicGamesと書いてある方ではないことに注意。
  • すると、「Clone」タブに切り替わりCloneする内容の設定をする。
  • ダウンロードする場所を適当に書き換え、「クローン」を押す。

    f:id:nakadaieng:20190716192659p:plain

全部ダウンロードするから時間かかると思いきや10分もかからないくらいで完了。

補足:Cloneされたブランチ名について

alweiさんの記事ではreleaseになっているが、私はmasterがcloneされた。
ドキュメントによるとプルリクはmasterをベースにしてと書いてあるのでこれでよいと思うが、forkの時点なのかcloneの時点なのか、元のブランチがどこで決まるのか不明。

ブランチ(SourceTree)

Histroyからmasterタグが付いてるコミットを選んで、
「右クリック>ブランチ」

  • ブランチ名は具体的な方がいいらしい。他のプルリクの名前を参考にして「Fix-SoundVisualizations-GetAmplitude」にした。
  • 新規のブランチを作成してチェックアウトにチェックを入れると、ブランチを作ってそのブランチをチェックアウトする(checkout -b の動作)。
    チェックをいれないと、branchコマンドでbranchをつくるだけ。

    f:id:nakadaieng:20190717162504p:plain

ソース書き換え~Commit~Push(SourceTree)

ソースを書き換えると
「コミットされていない変更があります」という行がHistoryの一番上に出る。

 

Commitは、
Historyじゃなくて、ファイルステータスのほうが作業しやすい。
下の「作業ツリーのファイル」に変更したファイルが出ているのを確認し、「全てインデックスに追加」を押す。これが add コマンドに相当する。
下部にコメントを書いて、「コミット」。
この段階のコメントは簡素でいいのかなぁ。とりあえずシンプルに「Fix GetAmplitude」とした。

 

そして「プッシュ」を押す。んー、この画面は難しいぞ・・・。

  • おそらくは、新たに作ったFix~ブランチのみをプッシュだろう。
    こちらの「対象」にチェックを入れると、自動的に「リモートブランチ」に同名が入った。
  • 「追跡中」はよくわからない。
  • 「すべてのタグをプッシュ」は要らんかった気がする。すべてのタグにup to dateってログが出てビビる。

    f:id:nakadaieng:20190717163152p:plain

GitHub上で、コミットが反映されていることを確認できる。
f:id:nakadaieng:20190717163941p:plain

Pull Request(GitHub

  • 「Compare & pull request」を押す

    f:id:nakadaieng:20190717164522p:plain

  • baseのブランチをmasterに変更する。

    と、差分情報が解析され「Able to merge」になった。
    「 Create pull request 」を押す。

    f:id:nakadaieng:20190717165034p:plain

  • Titleと内容を英語で頑張って書く。(Google翻訳でやりました!)

    (スクショ忘れた…)
  • プルリク完了!!

    f:id:nakadaieng:20190717165335p:plain

その後

すぐに返答が来ました!(実質1~2営業日くらい?)

f:id:nakadaieng:20190717165832p:plain

  • よりよいオーディオ解析API / Plugin Systemのために、このプラグインを非推奨にする予定なので、このプルリクエストは承認されません。
  • ブランチやフォークは削除してもいいよ。

てことでした!!
このプラグインがメンテナンスされてなくて消えそうなのはわかってた…。勉強になりました~!
 

 

なりゆきでエンジン修正してプルリクしてみた その1:プラグインをビルドして動かしてデバッグする【UE4 / Unreal Engine 4】

前置き

面白そうな記事が上がっていたので、見ながらいろいろ手を動かしていた。

kano-lab.org

標準の「Sound Visualizations Plugin」を使ってるんだけど、どうも「GetAmplitude」で取得するデータが変なので、記事内では仕方なく別の方法でやっている。ふーんと思って自分でもいろいろ調べるうちに、C++をビルドする環境を作って、GitHubの勉強をし、プルリクするまでに至ったという話。

先にバグの内容を書きます

やった作業の記録を書く前に、要するにこういうバグだったという部分だけ先に書いておきます。(結果的にそれはこの記事の本題じゃなくなっちゃったので。)

…わかってみれば単純で、データ取得開始位置の計算が間違っていた。
チャンネル数分のズレが考慮されていなかったので、2チャンネル(ステレオ)の時に、時間的にちょうど半分の位置から開始する(10秒の位置から取得しようとすると、5秒の位置から取得される)ようになっていた。
3チャンネル以上のファイルの場合は全く別の処理が組まれているので、問題が起きるのは2チャンネルのファイルのみである。

以下GitHubにアップした際の差分のスクショ:
GitHubがらみの話は別の投稿でします)

f:id:nakadaieng:20190716124516p:plain

その1:プラグインをビルドして動かしてデバッグする

C++環境

C++環境は、以前UE4C++本を見ながらセットアップしていた。

C++でつくるUnreal Engineアプリ開発 for Windows & macOS 〜初歩からプラグイン開発まで〜

C++でつくるUnreal Engineアプリ開発 for Windows & macOS 〜初歩からプラグイン開発まで〜

 

使用バージョンが本と異なるのでメモしておく。

OS:Windows10
US4:4.21.2
VisualStudio:2017 Community

 せっかくなんでVS Codeでやってみようかとも思ったけど、手間がかかるだけでメリットがなさそうなので素直にVSにした。

インストール時「C++によるデスクトップ開発」にチェックを入れる必要がある。他のネット上の記事にはWindows8.1とかVC++2015関連とかも入れないといけないって書いてあるものもあったけど、今は入れなくても大丈夫っぽい?少なくとも現状問題は出ていない。

もう2019が出てますね。昔のバージョンはこちらからDLできます。
https://visualstudio.microsoft.com/ja/vs/older-downloads/ 

空のプラグインを新規で作ってみる

できればエンジン全体はビルドしたくないので、自プロジェクトにプラグインだけコピーしてきてビルドする方針でやってみる。

 

まずはプラグインを作る手順を学ぶために空のプラグインをゼロから自作してみる。

この辺の記事を参考にしつつ…でもすぐに機能が変わってくるので差分は適当に解釈しつつ。
PluginBrowerのTemplate作成機能
https://qiita.com/BlackMa9/items/e9c99ee2bf652bb91064

以前はPluginCreaterだった
http://historia.co.jp/archives/2967/

 

1.空のC++プロジェクトを作成
VisualStudioも立ち上がるけどとりあえず閉じる。

2.エディタの「編集>Plugins」
下部の「New Plugin」ボタンから「Blank」のプラグインを作る。
これだけで、テンプレートのソースを追加してビルドするところまで自動で進む。
記事では再起動を促されるとかあったけどそんなこともなく完了。進化してるわ。

3.トレースデバッグするには
VS上でブレークポイントを張って、普通にF5すると、エディタが起動してブレークするのを確認。

 

何をした時にソリューションを作り直すべきなのかとか、どこまでが自動になっているのかとかっていうあたりがいまいちよくわからんなぁ。

BPプロジェクトをC++プロジェクトに変更する

このままこのプロジェクトで進めようかとも思ったけど、問題が起きている冒頭の記事の内容をまたこの新しいプロジェクト上に作るのが面倒なので、やっぱり既存のプロジェクトに新しくプラグインを入れていることにする。

と思ったら元の(問題が起きている)プロジェクトはBPプロジェクトで作り始めたので、C++環境がない。

.uprojectファイルを右クリック>Generate Visual Studio project filesで、環境ができる?と思ったが、以下のエラー:

f:id:nakadaieng:20190716121258p:plain

「This project dos not have any souece code. You need to add C++ souece
files to the project from the Editor befor you can generate project files.」

何らかのC++ソースを追加する必要がある?
ということで試しに、エディタから、「ファイル>新規C++クラス」から空のクラスを作ってみると、、、なんかslnファイルはできたが、ビルドエラー。
一旦エディタ閉じて「Generate Visual Studio project files」で作り直して、VSでビルドしても同じエラーが出る。

UnrealBuildTool : error : Couldn't find target rules file for target 'UE4Editor' in rules assembly 'MotionProject_03ModuleRules, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'.
Location:D:\*****\MotionProject_03\Intermediate\Build\BuildRules\MotionProject_03ModuleRules.dll
Target rules found:
MotionProject_03-D:\*****\MotionProject_03\Source\MotionProject_03.Target.cs
MotionProject_03Editor- D:\*****\MotionProject_03\Source\MotionProject_03Editor.Target.cs

 (一部伏字にしてます)

uprojectの、Enterpriseの行を消すとビルドが通るという情報があったので試したら通った・・・全く分からんけどとりあえずそういうこと。
https://answers.unrealengine.com/questions/835578/

だが、このslnからではF5でエディタが起動しない。エディタのexeの場所が分からなくなっちゃってるみたい。

プログラム 'D:\*****\Engine\Intermediate\Build\Unused\UE4.exe' を開始できません。
指定されたパスが見つかりません。 

まー設定があるんでしょうね。
今回は取り急ぎ、VSでソリューション開いておいて、エディタは別で起動しておいて、VS側で「デバッグ>プロセスにアタッチ」を開いて「UE4Editor」を選択することでトレースできたので面倒ですがこれで良しとする。 

BPプロジェクトをC++プロジェクトに変更する(補足)

自分のところで新規で作った「BPプロジェクト」を「C++プロジェクト」に変換してみたら、上記ビルドエラーも出ず、F5でエディタ実行もできた。

おそらく使用しているプロジェクトが冒頭で挙げたサイトからDLしたものをベースにしていたからではないかと思う。Enterpriseって、UnrealStudioのことかもしれない。

既存のプラグインを自プロジェクトでビルド

続いてエンジン側にある「Sound Visualizations Plugin」を自分のプロジェクト側にコピーしてみる。

この辺を参考にしつつ…
http://unrealengine.hatenablog.com/entry/2015/05/20/220000
http://historia.co.jp/archives/367/

まず、Pluginsフォルダを作り、その下に、
エンジン側の「\Engine\Plugins\Runtime\SoundVisualizations」
を丸っとコピー。BinariesとIntermediateは削除。

名前変えないとダメかなぁと思いつつとりあえずそのままでやってみる。
「Generate Visual Studio project files」でsln作り直す。
お、ビルドは通った。

ちょっといろいろやって前後関係が怪しいが
公式のプラグインが一覧に出てこなくなっちゃったけど、
追加した奴はProjectのプラグインとしてちゃんと一覧に出てきた。
一旦無効化(Enableチェックを外す)してから再度有効化し、
そのまま、使ってたBPを再生すると、プロジェクト側で追加した方のプラグインが使われているっぽい(ちゃんとそちらのブレークポイントに反応する)。
BPは名前で参照解決してるんだなぁ。

いわゆるホットリロードできないといちいちエディタ再起動で面倒だな。

http://unrealengine.hatenablog.com/entry/2018/01/07/224555
これでモジュール単位でホットリロードできるって。できたできた。

でも、エディタ側からコンパイルするとデバッグ情報がずれる?みたいで、VS側でブレークポイント置けなくなる。うーむ。今回のところはエディタ再起動します・・・。

不具合の原因特定 

…意外とてこずったが、結果、冒頭に書いたバグを発見しました。

続きます

次の記事で、プルリクの経緯を書きます。

電子工作はじめる

ハードウェアを絡めた何かをしたいと思っている。
ハードウェアの勉強は大昔に何度かチャレンジしたが全く理解できなかった。
改めてチャレンジする。

環境選び

最初に思い浮かんだのは「Raspberry Pi」。
そして「Arduino」読み方もわからん。…アルデュイーノですね了解。
調べると「Arduino」のほうがより素のハードウェアということのようだ。
ラズパイは要するに小さいPCでOS入れたりするらしい。
よしじゃあ「Arduino」で!
あんまり悩まないでどんどんやるというところを大事にしたい。

最初の一歩

スイッチサイエンスっていうショップがとてもいい雰囲気を醸し出している。

このキットと本がよさそうです。
よしこれ!
音とかモーターとかやりたいけどまあとりあえず着手してから。

 

では、ブツを手に入れたら進めます。