この質問と併せて。次の一見基本的な問題に対する適切な型安全な解決策を思いつくのに苦労しています。再生する曲のリストを持つクラス music_playlist があります。キュー内のすべての曲の std::list を作成し、ユーザーが利用できるようにするだけです。ただし、必要に応じて、オーディオのデコードとオーディオのレンダリングは別のスレッドで行われます。したがって、リストはミューテックスで保護する必要があります。多くの場合、私のライブラリを使用している他のプログラマーがミューテックスを忘れていました。これは明らかに「奇妙な」問題につながりました。
最初は、クラスのセッターを作成しました。
struct music{};
class music_playlist{
private:
std::list<music> playlist;
public:
void add_song(music &song){playlist.push_back(song);}
void clear(){playlist.clear();}
void set_list(std::list<music> &songs){playlist.assign(songs.begin(),songs.end());}
//etc
};
これにより、以下のようなユーザーコードが作成されました...
music song1;
music song2;
music song3;
music song4;
music song5;
music song6;
music_playlist playlist1;
playlist1.add_song(song1);
playlist1.add_song(song2);
playlist1.add_song(song3);
playlist1.add_song(song4);
playlist1.add_song(song5);
playlist1.add_song(song6);
//or
music_playlist playlist2;
std::list<music> songs;
songs.push_back(song1);
songs.push_back(song2);
songs.push_back(song3);
songs.push_back(song3);
songs.push_back(song5);
songs.push_back(song6);
playlist2.set_list(songs);
これは機能し、非常に明示的ですが。入力するのは非常に面倒であり、実際の作業に関するすべての面倒な作業により、バグが発生しやすくなります。これを実証するために、私は実際に上記のコードに意図的にバグを入れました。このようなものは簡単に作成でき、おそらくコード レビューをそのまま通過するでしょうが、song4 はプレイリスト 2 で再生されることはありません。
そこから、可変引数関数を調べました。
struct music{};
class music_playlist{
private:
std::list<music> playlist;
public:
void set_listA(music *first,...){
//Not guaranteed to work, but usually does... bleh
va_list Arguments;
va_start(Arguments, first);
if (first) {
playlist.push_back(first);
}
while (first) {
music * a = va_arg(Arguments, music*);
if (a) {
playlist.push_back(a);
}else {
break;
}
}
}
void set_listB(int count,music first,...){
va_list Arguments;
va_start(Arguments, first);
playlist.push_back(first);
while (--count) {
music a = va_arg(Arguments, music);
playlist.push_back(a);
}
}
//etc
};
以下のようなユーザーコードにつながります...
playlist1.set_listA(&song1,&song2,&song3,&song4,&song5,&song6,NULL);
//playlist1.set_listA(&song1,&song2,&song3,&song4,&song5,&song6); runtime error!!
//or
playlist2.set_listB(6,song1,song2,song3,song4,song5,song6);
曲が 2 回追加されたか、含まれていないかを簡単に確認できるようになりました。ただし、ソリューション A では、NULL がリストの最後になく、クロス プラットフォームでない場合にクラッシュします。solutionB では、いくつかのバグにつながる引数の量を数えなければなりません。また、いずれのソリューションもタイプ セーフではなく、ユーザーが無関係なタイプを渡して、実行時にクラッシュを待つ可能性があります。これは持続可能な解決策とは思えませんでした。それで、std::initializer_list に出会いました。私が開発したいくつかのコンパイラはまだサポートしていません。ということで真似してみました。私は以下のこの解決策に行き着きました。
これにより、ユーザーコードは次のようになります...
struct music_playlist{
list<music> queue;
//...
};
int main (int argc, const char * argv[])
{
music_playlist playlist;
music song1;
music song2;
music song3;
music song4;
playlist.queue = song1,song2; // The queue now contains song1 and song2
playlist.queue+= song1,song3,song4; //The queue now contains two song1s and song2-4
playlist.queue = song2; //the queue now only contains song2
return 0;
}
この構文は、私たちの小さなテストグループにとって混乱を招くものではありませんでした。しかし、私はオペレーターの過負荷をひどく乱用することに深刻な懸念を抱いていました。ということで、上記の質問を投稿しました。私たちのテスト グループに比べて比較的専門的なプログラマーが何を考えているかを知りたかったのです。多くのプログラマーはそれを好まなかったが、実行時だけでなくコンパイル時にほとんどのバグをキャッチできるため、上記のソリューションよりも優れているように思われた. しかし、Tom は興味深い反例を投稿しました。これは、予期しない動作を引き起こす可能性があります。
//lets combine differnt playlists
new_playlist.queue = song1 //the first playlist
,(song3,song4) //the second playlist //opps, I didn't add song 3!
, song5;
これは私をその解決策に悩ませます。では、より良い解決策について何かアイデアはありますか?
PS上記のコードはどれもコンパイルされていません。例としてのみ存在します。