プチ総合問題のつもりが、大掛かりな仕上がりとなってしまったため独自の記事を立てた。
例によって、出題はChatGPTによる。
第9問 簡易楽譜マネージャを作ろう (3/23)
問題
【概要】
あなたは、楽譜上の要素(小節や音符)をテキストベースで管理し、次の機能をシミュレートするコンソールアプリケーションを作成してください。
- スタッフ(五線)の上下移動
- 音符がある五線のインデックスを上下に動かす。
- 五線インデックスが範囲外にならないようにガードする。
- MMRest(マルチメジャーレスト 注:長休符)を含む小節リストの表示
- 小節ごとに「通常の小節」か「MMRest小節」か分かるように表示する。
- 例えば、3つの連続した休符小節は “MMRest(3)” として表示するイメージ。
- 「スナップ」機能のトグル
- 現在選択中の音符に対して「前の音符にスナップする/しない」を切り替える。
- 切り替えの結果をコンソールに出力する。
- 単純な「スラー」の適用シミュレーション
- 2つの音符を選択して「スラーを貼る」モードを作る。
- 「成功/失敗」の結果を表示。
【詳細要件】
- スタッフ上下移動
- ユーザーが「move-up」や「move-down」コマンドを入力したら、
- 選択中の Note の
staffIndexを +1/-1。 - ただし 0 と
(最大StaffIndex)の範囲内にクリップ。
- 選択中の Note の
- 結果を「noteID: X の staffIndex を n へ変更」などコンソール出力。
- ユーザーが「move-up」や「move-down」コマンドを入力したら、
- MMRest表示
- 一覧表示するコマンド (
show-measures) を実装し、Measureを順番に見てisMMRest == false→Measure #<measureID>isMMRest == true→MMRest(<mmRestCount>)
- もし同じ measureID をまとめて扱うようにしたいなら、まとめ対象の先頭 measure にだけ
isMMRest=trueを立てるといった管理でもOK。 - コンソール例:
1: Measure #12: MMRest(4) // ここは measureID=3 の小節がまとめ役3: ...
- 一覧表示するコマンド (
- スナップトグル
- 「toggle-snap <noteID>」と入力すると、該当 Note を探し、
snapToPreviousを反転する。 true -> false,false -> true- 結果を表示(「Note 10 snapToPrevious: OFF -> ON」など)。
- 「toggle-snap <noteID>」と入力すると、該当 Note を探し、
- スラー適用シミュレーション
- コマンド例:「apply-slur <startNoteID> <endNoteID>」
- 判定条件(例)
- 同じ
measureIDであること - 連符(tuplet)やトレモロなどはシミュレーション外なので今回は無視してOK
- 休符でも良いとするか? それとも音符同士か? など簡単に決める
- 同じ
- 成功の場合:「Slur applied from Note #X to Note #Y」
- 失敗の場合:「Failed to apply slur (reason: … )」
【データ構造】
1. Staff
- 目的
「五線」を管理する要素。ここでは数が固定でも良いし、可変でも良いですが、最低限以下の情報を持つとしましょう。 - メンバ
staffIndex(int)
五線の番号。0ベースでも1ベースでもいいですが、同じプロジェクト内では統一してください。name(std::string)
五線の名前(例: “Piano RH”, “Piano LH”, “Violin 1” など)。省略可。
- 備考
- 実装のシンプルさ重視なら「スタッフのリスト」を用意するのではなく、音符側で
staffIndexだけ持たせても問題ありません。 - ユーザーが「move-up/down」コマンドで五線を切り替えようとするとき、最大 staffIndex(全五線数 – 1)と 0 の範囲内で上下するイメージです。
- 実装のシンプルさ重視なら「スタッフのリスト」を用意するのではなく、音符側で
2. Measure
- 目的
楽譜の小節を管理する。 - メンバ
measureID(int)
小節番号。1小節目なら 1、など。isMMRest(bool)
マルチメジャーレストかどうか。mmRestCount(int)
その小節が MMRest である場合、何小節分の休符をまとめているかを示す。
たとえば、小節 3 ~ 5 がまとめられていて小節 3 の位置で表示するなら、mmRestCount = 3とか。
- MMRestCount のイメージ
- もし「3~6小節目が MMRest」に統合されたとしたら、小節 3 に相当する measure の
isMMRest = truemmRestCount = 4(3,4,5,6 の4小節分)
- 中間小節 4,5,6 は実際には「表示されない/スキップ扱い」でもいいし、簡易シミュレーションなら
isMMRest = falseで保持したままでもOK。 - どちらにしても、コンソール表示時に「MMRest(4)」のように表示し、その結果「3,4,5,6 がまとめられている」ことを示せば良い。
- もし「3~6小節目が MMRest」に統合されたとしたら、小節 3 に相当する measure の
3. Note
- 目的
楽譜上の音符・休符を管理する。 - メンバ
noteID(int)
ユニークな ID(apply-slur のときに引数に使うなど)。measureID(int)
どの小節に属しているか(小節との関連付け)。tick(int)
小節内での位置(拍・タイムの概念)。簡単に「0, 120, 240」など適当な値でよい。pitch(int)
音程を示す数値(60=中央ドなど)。休符なら特別な値(-1)を入れるでも可。staffIndex(int)
どの五線に配置されているか。move-up/downで +1/-1。snapToPrevious(bool)
「前の音符にスナップするかどうか」のフラグ(トグル動作)。
- 備考
- スラー適用チェックをする際、
measureIDが同じかどうか、休符(pitch == -1)でいいのか、など判定条件に使う。
- スラー適用チェックをする際、
【実装の流れ】
- データ作成
- まずは
std::vector<Measure>やstd::vector<Note>を適当に初期化(例: 5小節, 10ノートほど) staffIndexの最大値は 3(4本の五線)など決めておく- MMRestの例として、小節 3 を
isMMRest = true, mmRestCount=3にしてみる
- まずは
- メインループ
while(true)の中でユーザー入力を読み取り、コマンド名をif/elseかswitchで分岐show-measures→ 上記のメソッドで表示toggle-snap <noteID>→ 該当Note検索&フラグ反転move-up <noteID>/move-down <noteID>→ staffIndex調整apply-slur <startNoteID> <endNoteID>→ 条件チェック → 成否表示exit→ ループ終了
- 関数設計
showMeasures(const std::vector<Measure>& measures)toggleSnap(std::vector<Note>& notes, int noteID)moveStaff(std::vector<Note>& notes, int noteID, bool up, int maxStaffIndex)applySlur(const std::vector<Note>& notes, int startID, int endID)- 成功 or 失敗を
boolなどで返す
- 成功 or 失敗を
int main()でループ&入力解析
【操作イメージ】
- 起動後、ユーザーにコマンド入力を促す。例:
コマンドを入力してください:
1) show-measures
2) move-up
3) move-down
4) toggle-snap
5) apply-slur
6) exit
show-measures- 登録してある小節のリストを表示
move-up- 選択中の音符の
staffIndexを -1 して結果表示
- 選択中の音符の
move-downstaffIndexを +1
toggle-snapsnapToPreviousを false → true または true → false にして結果表示
apply-slur <startNoteId> <endNoteId>- 2つの音符IDを引数にとり、条件チェックして成功/失敗を表示
【提出物】
- メイン関数(
main())を含む C++ プログラム - コンソール入力でコマンドを受け、上記機能を実装
- 実行例のようにコンソール出力で操作の結果を表示
【ポイント】
- データ構造同士の関連付け
- Note が measureID を持つ
- Measure は measureID で管理
- staffIndex は Note が内包。スタッフの配列を作るかは自由
- 安全な操作
move-up/downで staffIndex がマイナスにならない or 上限超えないようガードtoggleSnapで Note が見つからない場合はエラー表示
- ユーザー体験
- コンソールメッセージを工夫して、何が起きたか分かりやすく出力
- 柔軟に簡略化して良い
- pitch, tick はあまり深くやらず、形だけ用意でもOK
- mmRestCount の運用は最低限で良い
この問題を解くと、これまで学んできた「五線インデックス」「スナップトグル」「連符や小節の整合性」「スラー適用条件」などの考え方をコンソールアプリで再現できるはずです。
「ソフトウェア設計の全体像に慣れてきたなら、こういう小さなシミュレーションを組んでみる」と、より深く身につくと思います。楽しんで取り組んでみてください!
最終解答
#include <iostream>
#include <vector>
struct Staff {
int staffIndex; //0start
std::string name; //part-name
};
struct Measure {
int measureID;
bool isMMrest; //長休符最初の小節だけtrue
int mmRestCount; //長休符最初の小節だけ数値変更
};
struct Note {
int noteID;
int measureID;
int tick; //0~999 を想定
int pitch; //-1は休符、0~127は音符
int staffIndex;
bool snapToPrevious;
};
//noteIDに対応するnotesのポインタを取得
Note* findNote(std::vector<Note>& notes, int noteID){
for(auto& n : notes){
if(n.noteID == noteID){
return &n;
}
}
return nullptr;
}
//不正でないnoteIDをユーザーから取得
int getNoteID(std::vector<Note>& notes){
int id = 0;
while(true){
std::cout << "noteIDを入力...";
std::cin >> id;
if (findNote(notes, id)){
break;
}else{
std::cout << "エラー:noteID が見つかりません。\n";
}
}
return id;
}
void showMeasures(const std::vector<Measure>& measures){
std::cout << "\n[measures List]" << std::endl;
int restCnt = 0;
for(auto& m : measures){
if(m.isMMrest){
std::cout << "MMRest(" << m.mmRestCount << ")" << std::endl;
restCnt = m.mmRestCount - 1;
}else{
if(restCnt > 0){
restCnt--;
}else{
std::cout << "Measure #" << m.measureID << std::endl;
}
}
}
std::cout << std::endl;
}
void moveStaff(std::vector<Note>& notes, int noteID, bool up, int maxStaffIndex){
//noteIDは信頼できる
Note* target = findNote(notes, noteID);
if(!target){ //念のため
std::cout << "[move up/down] エラー:noteID が見つかりません。" << std::endl;
return;
}
int before = target->staffIndex; //元の段
if(up){ //上へ移動、-1
if(target->staffIndex > 0){
target->staffIndex--;
}else{
std::cout << "[move-up] 移動不可" << std::endl;
}
std::cout << "[move-up] Note " << noteID << ": staffIndex " << before;
std::cout << " -> " << target->staffIndex << std::endl;
}else{
if(target->staffIndex != maxStaffIndex-1){
target->staffIndex++;
}else{
std::cout << "[move-down] 移動不可" << std::endl;
}
std::cout << "[move-down] Note " << noteID << ": staffIndex " << before;
std::cout << " -> " << target->staffIndex << std::endl;
}
}
void toggleSnap(std::vector<Note>& notes, int noteID){
//noteIDは信頼できる
Note* target = findNote(notes, noteID);
if(!target){ //念のため
std::cout << "[toggle-snap] エラー:noteID が見つかりません。" << std::endl;
return;
}
target->snapToPrevious = !target->snapToPrevious;
std::cout << "[toggle-snap] Note " << noteID << ": toggle-snap ";
std::cout << (!target->snapToPrevious ? "ON" : "OFF") << " -> ";
std::cout << (target->snapToPrevious ? "ON" : "OFF") << std::endl;
}
void applySlur(std::vector<Note>& notes, int startID, int endID){
Note* target1 = findNote(notes, startID);
Note* target2 = findNote(notes, endID);
if(!target1 || !target2){ //念のため
std::cout << "[apply-slur] エラー:noteID が見つかりません。" << std::endl;
return;
}
//target1,2の前後関係チェック
if(startID == endID){
std::cout << "[apply-slur] エラー:スラーの始点と終点が同じです。" << std::endl;
return;
}else if(startID > endID){
std::swap(startID, endID);
std::swap(target1, target2);
}
//スラーしてよいかチェック(両方音符、同じstaffIdx)
if(target1->pitch == -1 || target2->pitch == -1){
std::cout << "[apply-slur] エラー:スラーは休符に適用できません。" << std::endl;
return;
}else if(target1->staffIndex != target2->staffIndex){
std::cout << "[apply-slur] エラー:スラーは別の五線の音符に適用できません。" << std::endl;
return;
}
std::cout << "[apply-slur] Note " << startID << " -> " << endID << " (表示のみ)" << std::endl;
}
int main()
{
//テストデータ設定 後々初期化拡張も検討
std::vector<Staff> staffs = {
Staff{0, "Violin 1"},
Staff{1, "Violin 2"},
Staff{2, "Viola"},
Staff{3, "Cello"}
};
std::vector<Measure> measures = {
Measure{1, false, 0},
Measure{2, false, 0},
Measure{3, true, 4}, // MMRest(4): 3-6小節
Measure{4, false, 0},
Measure{5, false, 0},
Measure{6, false, 0},
Measure{7, false, 0}
};
std::vector<Note> notes = {
Note{1, 1, 0, 60, 0, false},
Note{2, 1, 240, 62, 0, false},
Note{3, 2, 0, 64, 1, true},
Note{4, 2, 240, 65, 1, false},
Note{5, 3, 0, -1, 2, true}, // rest
Note{6, 5, 0, 67, 2, false}
};
/*
//テストデータ確認出力
std::cout << "staffs" << std::endl;
for(auto& s: staffs){
std::cout << s.staffIndex << " " << s.name << std::endl;
}
std::cout << "measures" << std::endl;
for(auto& m : measures){
std::cout << m.measureID << " " << m.isMMrest << " " << m.mmRestCount << std::endl;
}
std::cout << "notes" << std::endl;
for(auto& n : notes){
std::cout << n.noteID << " " << n.measureID << " " <<
n.tick << " " << n.pitch << " " << n.staffIndex << " " << n.snapToPrevious << std::endl;
}
*/
//コンソール入力受付
std::cout << "コマンドを入力...\n";
std::cout << "1) show-measures\n2) move-up\n3) move-down\n";
std::cout << "4) toggle-snap\n5) apply-slur\n6) exit" << std::endl;
while(true){
std::string cmd;
std::cin >> cmd;
if (cmd == "show-measures"){
showMeasures(measures);
}else if(cmd == "move-up"){
moveStaff(notes,getNoteID(notes),true,4);
}else if(cmd == "move-down"){
moveStaff(notes,getNoteID(notes),false,4);
}else if(cmd == "toggle-snap"){
toggleSnap(notes,getNoteID(notes));
}else if(cmd == "apply-slur"){
int start;
int end;
std::cout << "[apply-slur] Start: ";
start = getNoteID(notes);
std::cout << "[apply-slur] End: ";
end = getNoteID(notes);
applySlur(notes,start,end);
}else if(cmd == "exit"){
break;
}else{
std::cout << "正しいコマンドを入力してください。" << std::endl;
}
}
std::cout << "終了" << std::endl;
return 0;
}最終解答(2回目、4/7)
#include <iostream>
#include <vector>
#include <sstream>
struct Staff{
int staffIndex; //0 start
std::string name;
};
struct Measure{
int measureID; //1 start
bool ismmRest;
int mmRestCount; //最初の長休符小節のみ格納
};
struct Note{
int noteID;
int tick;
int pitch; //休符 -1
bool snapToPrevious;
const Staff* staff;
const Measure* measure;
};
//Note構造体 の noteID と 添え字対応づけ
Note* findNote(std::vector<Note>& notes, int noteID){
for(auto& n : notes){
if(n.noteID == noteID){
return &n;
}
}
return nullptr;
}
void showMeasures(const std::vector<Measure>& measures){
int idx = 1;
for(int i=0; i<measures.size(); ++i){
if(!measures[i].ismmRest){
std::cout << idx << ": Measure #" << measures[i].measureID << std::endl;
++idx;
}else{
std::cout << idx << ": MMRest(" << measures[i].mmRestCount << ")" << std::endl;
i += measures[i].mmRestCount - 1;
++idx;
}
}
}
bool moveStaff(std::vector<Note>& notes, int noteID, bool up, const std::vector<Staff>& staffs){
Note* target = findNote(notes, noteID);
if(!target){
std::cout << "[Error] noteIDが存在しません。" << std::endl;
return false;
}
int beforeIndex = target->staff->staffIndex;
if(up && beforeIndex == 0){
std::cout << "[Error] noteID: " << noteID << " は最上段です。" << std::endl;
return false;
}
if(!up && beforeIndex == staffs.size() - 1){
std::cout << "[Error] noteID: " << noteID << " は最下段です。" << std::endl;
return false;
}
target->staff = up ? &staffs[beforeIndex - 1] : &staffs[beforeIndex + 1];
std::cout << "[Done] noteID:" << noteID << " staff:" <<
beforeIndex << " -> " << target->staff->staffIndex << std::endl;
return true;
}
void toggleSnap(std::vector<Note>& notes, int noteID){
Note* target = findNote(notes, noteID);
if(!target){
std::cout << "[Error] noteIDが存在しません。" << std::endl;
return;
}
target->snapToPrevious = !target->snapToPrevious;
std::cout << std::boolalpha <<
"[toggleSnap] NoteID: " << noteID << " , snapToPrevious: " <<
!target->snapToPrevious << " -> " << target->snapToPrevious << std::endl;
}
bool applySlur(std::vector<Note>& notes, int startID, int endID){
Note* target1 = findNote(notes, startID);
Note* target2 = findNote(notes, endID);
if(!target1 || !target2){
std::cout << "[Error] 存在しないnoteIDが入力されました。" << std::endl;
return false;
}
if(target1 == target2){
std::cout << "[Error] 同じnoteIDを指定しています。" << std::endl;
return false;
}
if(target1->pitch == -1 || target2->pitch == -1){
std::cout << "[Error] 休符にスラーを適用できません。" << std::endl;
return false;
}
if(target1->staff != target2->staff){
std::cout << "[Error] 違う段の音符にスラーを適用できません。" << std::endl;
return false;
}
std::cout << "[done] スラー (noteID: " << startID << " - " << endID << ")" << std::endl;
return true;
}
int main()
{
//テストデータ設定
std::vector<Staff> staffs = {
Staff{0, "Violin 1"},
Staff{1, "Violin 2"},
Staff{2, "Viola"},
Staff{3, "Cello"}
};
std::vector<Measure> measures = {
Measure{1, false, 0},
Measure{2, false, 0},
Measure{3, true, 4}, // MMRest(4): 3-6小節
Measure{4, false, 0},
Measure{5, false, 0},
Measure{6, false, 0},
Measure{7, false, 0}
};
std::vector<Note> notes = {
Note{1, 0, 60, false, &staffs[0], &measures[0]}, // Violin 1, Measure 1
Note{2, 240, 62, false, &staffs[0], &measures[0]}, // Violin 1, Measure 1
Note{3, 0, 64, true, &staffs[1], &measures[1]}, // Violin 2, Measure 2
Note{4, 240, 65, false, &staffs[1], &measures[1]}, // Violin 2, Measure 2
Note{5, 0, -1, true, &staffs[2], &measures[2]}, // Viola (休符), Measure 3
Note{6, 0, 67, false, &staffs[2], &measures[4]} // Viola, Measure 5
};
// コマンド受け取り
std::string line, cmd;
while(true){
std::cout << "コマンドを入力してください。[h]コマンド一覧表示...";
std::getline(std::cin, line);
std::istringstream iss(line);
std::vector<std::string> tokens;
std::string token;
while(iss >> token){
tokens.push_back(token);
}
if (tokens.empty()) continue;
cmd = tokens[0];
if (cmd == "h"){
std::cout << "(1) show-measures [s]\n(2) move-up [mu] <noteID>\n" <<
"(3) move-down [md] <noteID>\n(4) toggle-snap [t] <noteID>\n" <<
"(5) apply-slur [a] <startNoteID> <endNoteID>\n(6) exit [e]" << std::endl;
}
else if (cmd == "show-measures" || cmd == "s"){
showMeasures(measures);
}
else if ((cmd == "move-up" || cmd == "mu") && tokens.size() == 2){
int arg1 = std::stoi(tokens[1]);
bool success = moveStaff(notes, arg1, true, staffs);
std::cout << "[move-up] " << (success ? "成功" : "失敗") << std::endl;
}
else if ((cmd == "move-down" || cmd == "md") && tokens.size() == 2){
int arg1 = std::stoi(tokens[1]);
bool success = moveStaff(notes, arg1, false, staffs);
std::cout << "[move-down] " << (success ? "成功" : "失敗") << std::endl;
}
else if ((cmd == "toggle-snap" || cmd == "t") && tokens.size() == 2){
int arg1 = std::stoi(tokens[1]);
toggleSnap(notes, arg1);
}
else if ((cmd == "apply-slur" || cmd == "a") && tokens.size() == 3){
int arg1 = std::stoi(tokens[1]);
int arg2 = std::stoi(tokens[2]);
bool success = applySlur(notes, arg1, arg2);
std::cout << "[apply-slur] " << (success ? "成功" : "失敗") << std::endl;
}
else if (cmd == "exit" || cmd == "e"){
std::cout << "プログラムを終了します。" << std::endl;
break;
}
else{
std::cout << "[Error] 不正なコマンドか、引数の数が正しくありません。" << std::endl;
}
}
return 0;
}ふりかえり
- 完成に4時間半かかってしまった。
- 仕様を読んだ時点では、「基本的なC++構文の組み合わせでできそう、でも面倒くさそう」という感想。でも手をつけてみるとそんな簡単ではなく…。
- 以下のとおり、実践的な1問を徹底的に解くと大きな収穫がある。自分のできなさに直面する。
- クラスと構造体の区別
- メソッドがなくてデータ構造として扱うだけなら構造体
- 構造体はデフォルトでpublic、クラスはデフォルトでprivate
- データを隠したい、継承やポリモーフィズムを使いたいならクラスにするとよい
- どこまで汎用性を意識するか? 要求に忠実になるか? に悩む。
- この仕様の前提として、小節数、五線数の初期化をすべきでは?とまず考える。
- しかし、まずは求められたことだけを作る姿勢も大事。
- switch文の条件に文字列 std::string は使えない。
- 関数の責任分担、変数の宣言位置の悩み
- この変数をどこで宣言してどのスコープで使うべき?
- 実装したい機能をどの関数、位置で組み込めば良い? と悩んで時間が掛かる。
- ここでこうしたら全体の整合性は? ここで変数作ってスコープミスらない? この処理をどの関数の中に入れると後で困らない?
- 関数を複数作ると、役割が重複していないかが気になる。
- 例:①NoteIDの入力、②入力を受けたNoteIDが存在するかのチェック、③NoteIDとそれを指すポインタの紐付け、④Noteの中身を編集する機能の実装 →それぞれの役割をどの関数に割り振るか、また処理の階層構造を考える
- 引数の扱い(定数として扱うか変数として扱うか)
- 関数Aで引数Xをconstで取ったのに、その関数Aの中で別の関数Bを呼び出し、Bにその引数Xを非constで渡そうとするとエラーになる(定数の中身を書き換えうる関数に渡しているので)
- 関数Aで引数Xをconstで取ったのに、Xに含まれる一部の値を非constでreturnしようとするとエラーになる→戻り値もconstとなるように、関数の定義で
const void...とすべき
- ポインタとconst
const Note* p:pが指すNoteの書き換え不可、pの付け替え(別のNoteに充てる)は可
Note* const p:pが指すNoteの書き換え可、pの付け替え不可 - vectorのインデックスと人間が設定したIDは一致しない
- ここを勘違いするとバグの温床
- 例:
notes.noteID == 6とnotes[6]は示すものが違う
- 複数の引数を入力させる場合の処理
- 各引数(個別)のチェックは専用の関数を作るとよい
- 引数同士の論理的な関係性は処理関数のなかで判断する
- 例:スラー適用で両端のNoteID(x,y)をあわせて2つ取る場合
- x, y がそれぞれNoteIDに属する値か? →専用の関数でチェック
- x, y の値の大小関係や不一致であるか? →スラー適用関数でチェック
ふりかえり (2回目)
- 所要時間:2時間程度。前回と同じレベルのたたき台コードを書き切った。
- コマンドによって可変の引数を受け取りたい
- 前回は、コマンド受付→分岐させて必要数の引数を受け取った。今回は
コマンド+NoteIDといった形式で一気に受け取りたい。 std::getline() + std::istringstreamstd::getline(): 1行の入力をまるごと文字列として受け取る。- cf.
std::cinは空白で区切られて1単語しか読めない std::istringstream: 文字列をストリーム化(?)、わからん- istringstream は stringのような型になるの?
- この場合、不要な引数が残るケースがある。
- →毎回コマンド入力を受けるたびに、引数の数をチェックする仕組みを作る。
- 前回は、コマンド受付→分岐させて必要数の引数を受け取った。今回は
if(A){…}; if(B){…};を上手に使い、ダメな例を次々弾いて最後に本命処理。- 92~110行目の処理が流れるようにキレイになった。
- 変数名
countvs.numcount: いまの状態を測るイメージ(vectorのサイズ、ループの終了条件)num: 与えられた定数・仕様としての数値というイメージ
- 設計の整合性を考える
applySlur()を bool型にして、成功失敗を明示するようになった- →
moveStaff()も bool型にしたほうが整合的では?
- Note構造体で、Staff(五線位置)とMeasure(小節)をポインタにすることで、Staff構造体とMeasure構造体との整合性を取る。データとしての一貫性を意識した設計をする。
- データ同士を繋げられるようになると、一気に意味を帯びる。
- 他の構造体をポインタで扱うようになると、実装部分で比較したいときに、「何を比較するか」が変わる。
- 具体的な値で比較 vs. 指している構造体の実体が同じかをポインタで比較。→ポインタ比較が安全。
- あるNoteの
staffIndexを変更したい(その音符を上の五線に移動させたい) →staff->staffIndexの値を書き換えてしまうミス- これをすると、Staff 構造体の要素の値を書き換えてしまう。
- 例えば、上から3番目の五線の
staffIndexの値を2→1に書き換えてしまう。当該 Note の所属する五線が3番目から2番目に移るわけではない。 - 構文上の誤りはなくコンパイルが通ってしまうので怖い。
- そうすると、「
&staffsが今のスコープに存在しない」とのコンパイルエラーが出る。- ポインタを使うようになると、外から引数で渡す必要が出る。
- そうすると、
staffをconstにしたほうが安全になる。意図せず書き換えてしまうバグを防げるから。 - そうすると、処理の内部で
const Staff*をStaff*に代入することになり、コンパイルエラー。- 今の処理で書き換えなかったとしても、「書き換える可能性がある」だけでC++はエラーを吐く。
- そのため、Note構造体の定義から、
Staff, Measureをconst指定しないといけない。C++はこういうところにとても厳しいのだ。
- C++は本気で信頼されるシステムを書くための言語。責任の重さ=自由の重さ(byAI MEMちょ)
