オーバーロード演算子 << - C++
-
03-07-2019 - |
質問
背景
内部的にvector<std::string>を使用するコンテナクラスがあります。方法を提供しました AddChar(std::string) を行うこのラッパークラスに プッシュバック() 内部ベクトルに。私のコードでは、コンテナに複数の項目を追加する必要があります。そのために私は使わなければなりません
container.AddChar("First");
container.AddChar("Second");
これによりコードが大きくなります。そこで、より簡単にするために、演算子 << をオーバーロードする予定です。書けるように
container << "First" << "Second"
2 つの項目が基になるベクターに追加されます。
これに私が使用したコードがあります
class ExtendedVector
{
private:
vector<string> container;
public:
friend ExtendedVector& operator<<(ExtendedVector& cont,const std::string str){
cont.AddChar(str);
return cont;
}
void AddChar(const std::string str)
{
container.push_back(str);
}
string ToString()
{
string output;
vector<string>::iterator it = container.begin();
while(it != container.end())
{
output += *it;
++it;
}
return output;
}
};
期待どおりに動作しています。
質問
- 演算子のオーバーロードは正しく記述されていますか?
- このような状況で演算子をオーバーロードすることは良い習慣ですか?
- このコードにはパフォーマンスの問題やその他の問題が発生しますか?
何かご意見は?
編集
素晴らしいコメントを聞いた後、ここでは意味が無いので、<< をオーバーロードしないことにしました。演算子のオーバーロード コードを削除しました。最終的なコードは次のとおりです。
class ExtendedVector
{
private:
vector<string> container;
public:
ExtendedVector& AddChar(const std::string str)
{
container.push_back(str);
return *this;
}
.. other methods
}
これにより、追加できるようになります
container.AddChar("First").AddChar("Second")
C# では、params キーワードを使用すると、これをより簡単に行うことができます。コードは次のようになります
void AddChar(params string[] str)
{
foreach(string s in str)
// add to the underlying collection
}
C++ では使用できることはわかっています ... パラメータの可変長を指定します。しかし、私の知る限り、それはタイプセーフではありません。それでは、そうすることは推奨される習慣なのでしょうか?書けるように
container.AddChar("First","Second")
ご返信ありがとうございます。
解決
演算子のオーバーロードは正しく記述されていますか?
それはそうなのですが、もっと良くできるはずです。他の誰かが言及したように、関数は既存のパブリック関数から完全に定義できます。なぜそれらだけを使用しないのですか?現時点ではフレンドです。つまり、実装の詳細に属しています。Operator<< をクラスのメンバーとして置いた場合も同様です。ただし、演算子<< a を作成します。 非会員, 友達ではない 関数。
class ExtendedVector {
...
};
// note, now it is *entirely decoupled* from any private members!
ExtendedVector& operator<<(ExtendedVector& cont, const std::string& str){
cont.AddChar(str);
return cont;
}
クラスを変更した場合、operator<< が引き続き機能するかどうかはわかりません。ただし、operator<< がパブリック関数のみに完全に依存している場合は、クラスの実装の詳細にのみ変更を加えた後でも動作することを確認できます。わーい!
このような状況で演算子をオーバーロードすることは良い習慣ですか?
別の人が再び言ったように、これには議論の余地があります。多くの状況で、演算子のオーバーロードは一見すると「きちんと」しているように見えますが、来年には地獄のようになります。なぜなら、いくつかのシンボルに特別な愛を与えるときに何を念頭に置いていたのか、もはや見当がつかないからです。演算子<<の場合、これは問題なく使用できると思います。ストリームの挿入演算子としての使用はよく知られています。そして、私は次のような場合にそれを広範囲に使用する Qt および KDE アプリケーションを知っています。
QStringList items;
items << "item1" << "item2";
同様のケースは、 boost.format
これも再利用します operator%
文字列内のプレースホルダーの引数を渡す場合:
format("hello %1%, i'm %2% y'old") % "benny" % 21
もちろん、そこで使用することも議論の余地があります。しかし、printf フォーマット指定での使用はよく知られているので、そこでの使用も問題ありません。しかし、いつものように、スタイルは主観的なものでもあるので、話半分に聞いてください :)
タイプセーフな方法で可変長の引数を受け入れるにはどうすればよいですか?
そうですね、同種の引数を探している場合は、ベクトルを受け入れる方法があります。
void AddChars(std::vector<std::string> const& v) {
std::vector<std::string>::const_iterator cit =
v.begin();
for(;cit != v.begin(); ++cit) {
AddChar(*cit);
}
}
ただし、それを通過するのはあまり快適ではありません。手動でベクトルを構築してから渡す必要があります...vararg スタイルの関数についてはすでに正しく理解されているようですね。この種のコードにはこれらを使用せず、C コードとインターフェイスする場合や関数をデバッグする場合にのみ使用してください。このケースに対処するもう 1 つの方法は、プリプロセッサ プログラミングを適用することです。これは高度なトピックであり、かなりハック的です。アイデアは、おおよそ次のように、ある上限までのオーバーロードを自動的に生成することです。
#define GEN_OVERLOAD(X) \
void AddChars(GEN_ARGS(X, std::string arg)) { \
/* now access arg0 ... arg(X-1) */ \
/* AddChar(arg0); ... AddChar(arg(N-1)); */ \
GEN_PRINT_ARG1(X, AddChar, arg) \
}
/* call macro with 0, 1, ..., 9 as argument
GEN_PRINT(10, GEN_OVERLOAD)
それが疑似コードです。ブースト プリプロセッサ ライブラリをご覧ください。 ここ.
次の C++ バージョンは、はるかに優れた可能性を提供します。初期化子リストは次のように使用できます。
void AddChars(initializer_list<std::string> ilist) {
// range based for loop
for(std::string const& s : ilist) {
AddChar(s);
}
}
...
AddChars({"hello", "you", "this is fun"});
次の C++ では、次の方法を使用して、任意の多数の (混合型) 引数をサポートすることも可能です。 可変個引数テンプレート. 。GCC4.4 ではそれらをサポートする予定です。GCC 4.3 はすでにそれらを部分的にサポートしています。
他のヒント
1)はい。ただし、AddCharはパブリックなので、 friend
である必要がある理由はありません。
2)これは議論の余地があります。 &lt;&lt;
は、&quot; weird&quot;のオーバーロードが発生する演算子であるという立場にあります。物事は少なくともしぶしぶ受け入れられます。
3)明らかなことは何もありません。いつものように、プロファイリングはあなたの友達です。回避するために、文字列パラメーターをconst参照( const std :: string&amp;
)によって AddChar
および operator&lt;&lt;
に渡すことを検討してください。不要なコピー。
オーバーロードすることをお勧めします このような状況での演算子?
そうは思いません。あなたがオペレータをオーバーロードしたことを知らない人にとっては、それは地獄として混乱します。わかりやすいメソッド名に固執し、入力する余分な文字を忘れてください。それだけの価値はありません。あなたのメンテナー(または6か月以内にあなた自身)に感謝します。
ベクターには通常、オーバーロードされた左シフト演算子がないため、個人的にその方法でオーバーロードしたくない-実際にはイディオムではない;-)
おそらくそうではなく、AddCharから参照を返します:
ExtendedVector& AddChar(const std::string& str) {
container.push_back(str);
return *this;
}
だからできること
container.AddChar("First").AddChar("Second");
ビットシフト演算子よりもそれほど大きくありません。
(値ではなく参照による文字列の受け渡しに関するLoganのコメントも参照してください。)
この場合の演算子のオーバーロードは、コードを読みにくくするため、良い習慣ではありません。標準の std :: vector
には、正当な理由から要素をプッシュするための機能もありません。
呼び出し元のコードが長すぎることが心配な場合は、オーバーロードされた演算子の代わりにこれを検討できます。
container.AddChar("First").AddChar("Second");
AddChar()
が * this
を返す場合、これが可能になります。
この toString()
関数があるのは面白いです。 その場合、ストリームに出力する operator&lt;&lt;
が代わりに使用する標準的なものです!したがって、演算子を使用する場合は、 toString()
関数を operator&lt;&lt;
にします。
ここで演算子は正しくオーバーロードされていません。クラスのメンバーになることができるため、オペレーターを友達にする理由はありません。友人は、クラスの実際のメンバーではない関数のためです(オブジェクトをcoutまたはofstreamsに出力できるようにostreamの&lt;&lt;をオーバーロードする場合など)。
実際に演算子にしたいこと:
ExtendedVector& operator<<(const std::string str){
AddChar(str);
return *this;
}
通常、通常よりも何かを行う方法で演算子をオーバーロードすることは、悪い習慣と見なされます。 &lt;&lt;通常はビットシフトであるため、この方法でオーバーロードすると混乱する可能性があります。明らかにSTLのオーバーロード&lt;&lt; 「ストリーム挿入」用そのため、同様の方法で使用するためにオーバーロードするのが理にかなっています。しかし、それはあなたがしていることのようには見えないので、おそらくそれを避けたいでしょう。
演算子のオーバーロードは通常の関数呼び出しと同じであるため、パフォーマンスの問題はありません。コンパイラーによって自動的に行われるため、呼び出しだけが非表示になります。
これはかなり混乱を招きます。変数にstd :: cinと同じ構文を使用します:
std :: cin&gt;&gt; someint;
&quot; First&quot; &gt;&gt;コンテナ;
このように、少なくとも挿入演算子です。何かが&lt;&lt;を持っているときオーバーロードされた演算子std :: coutと同じように。