演算子をオーバーロードする方法<<それはostreamを受け取ったり返したりしません
-
08-07-2019 - |
質問
元の質問
目標を達成することを目的としたロギングクラスを作成しています:
// thread one
Logger() << "Some string" << std::ios::hex << 45;
// thread two
Logger() << L"Some wide string" << std::endl;
現在、私のLoggerヘッダーは次のようになっています。
#pragma once;
#include <ostream>
class Logger
{
public:
Logger();
~Logger();
std::ostream* out_stream;
};
template <typename T>
Logger& operator<< (Logger& logger, T thing) {
*logger.out_stream << thing;
return logger;
}
このクラスに関する注意事項:
- クロスプラットフォームの互換性は問題ではありません。
- Logger.cppの内部には、「本物」を作成するシングルトンクラスがあります。 ostream。
- Loggerコンストラクターとデコンストラクターは、シングルトンの必要なロックを実行します。
3つの問題があります:
- 演算子を作成する方法&lt;&lt; out_streamをプライベートとして設定できるように、友人またはメンバーを機能させますか?
- 演算子を作成する方法&lt;&lt;関数はマニピュレーターで動作しますか?
- TがWCHAR *またはstd :: wstringの場合、out_streamに渡す前にchar *またはstd :: stringに変換するように特殊化を追加するにはどうすればよいですか? (変換を行うことができます。私の場合、高いユニコード文字を失うことは問題ではありません。)
回答で学んだことの要約:
- テンプレートを後ではなく友人の前に置きます。
- std :: ios :: hexはマニピュレーターではありません。 std :: hexはマニピュレーターです。
最終結果
#pragma once
#include <ostream>
#include <string>
std::string ConvertWstringToString(std::wstring wstr);
class Logger
{
public:
Logger();
~Logger();
template <typename T>
Logger& operator<< (T data) {
*out << data;
return *this;
}
Logger& operator<< (std::wstring data) {
return *this << ConvertWstringToString(data);
}
Logger& operator<< (const wchar_t* data) {
std::wstring str(data);
return *this << str;
}
private:
std::ostream* out;
};
解決
フレンド定義を使用して、クラスの周囲のネームスペースで演算子を定義し、演算子のオーバーロード解決のみに可視にすることができます(:: operator&lt;&lt; ...構文を使用して手動で呼び出すことはできません):
class Logger
{
public:
Logger();
~Logger();
std::ostream* out_stream;
template <typename T>
friend Logger& operator<< (Logger& logger, T thing) {
*logger.out_stream << thing;
return logger;
}
/* special treatment for std::wstring. just overload the operator! No need
* to specialize it. */
friend Logger& operator<< (Logger& logger, const std::wstring & wstr) {
/* do something here */
}
};
代わりに、コードをそのままにして、単に演算子を作成します&lt;&lt;友人をテンプレート化するには、次の行をクラス定義に追加します。
template <typename T>
friend Logger& operator<< (Logger& logger, T thing);
マニピュレーターの問題については、少し前に書いたコードを提供します。
#include <iostream>
#include <cstdlib>
using namespace std;
template<typename Char, typename Traits = char_traits<Char> >
struct logger{
typedef std::basic_ostream<Char, Traits> ostream_type;
typedef ostream_type& (*manip_type)(ostream_type&);
logger(ostream_type& os):os(os){}
logger &operator<<(manip_type pfn) {
if(pfn == static_cast<manip_type>(std::endl)) {
time_t t = time(0);
os << " --- " << ctime(&t) << pfn;
} else
os << pfn;
return *this;
}
template<typename T>
logger &operator<<(T const& t) {
os << t;
return *this;
}
private:
ostream_type & os;
};
namespace { logger<char> clogged(cout); }
int main() { clogged << "something with log functionality" << std::endl; }
};
これはstd :: hexですが、std :: ios :: hexではないことに注意してください。後者は、ストリームの setf
関数のマニピュレーターフラグとして使用されます。 thoの例では、マニピュレーターの特別な処理は必要ありません。上記のstd :: endlの特別な処理は、std :: endlが使用されているときにさらに時間をストリーミングするためにのみ必要です。
他のヒント
テンプレートを使用するのが正しい方法ですが、テンプレートが header ファイル( logger.h
など)にあることを確認するだけです。呼び出し)、実装ファイル( logger.cpp
)にはない。これは、 std :: ostream
で operator&lt;&lt;
が定義されているすべてのタイプで自動的に機能します。また、ストリームマニピュレータオブジェクトも自動的に機能します。これらは実際には std :: ostream
パラメータを取る関数であり、 operator&lt;&lt;
は単に関数を呼び出します ostream
。
次のように operator&lt;&lt;
をフレンド関数にできます:
template <typename T> friend Logger& operator<< (Logger& logger, T thing);
特化は簡単です-テンプレートの特化を使用するだけです(再び、ヘッダーファイルで):
template <typename T>
Logger& operator<< (Logger& logger, T thing) {
*logger.out_stream << thing;
return logger;
}
// Template specialization - the "template <>" part is necessary
template <>
Logger& operator<< (Logger& logger, const wchar_t *wstr)
{
// convert wstr to an ANSI string and log it
}
template <>
Logger& operator<< (Logger& logger, const std::wstring & wstr)
{
// convert wstr to an ANSI string and log it
}
友情宣言は必要ありません:
class Logger
{
public:
Logger();
~Logger();
template <typename T>
inline Logger& Display(T thing)
{
*out_stream << thing;
return *this;
}
private:
std::ostream* out_stream;
};
template <typename T>
Logger& operator<< (Logger& logger, T thing)
{
return logger.Display(thing);
}
なぜprintf方式ではなく、マルチパラメータ方式を使用するのか(3つのドットで...)。これはまだ多くのフォーマット機能を提供し、&lt;&lt;を使用しているときのように面倒になりません。
例:
Logger("This is my log msg %0X", 45);
2秒間待機して、コードサンプルを不用意にプルアップします。
編集:
void Logger(const char* format, ...)
{
char szMsg[3000];
va_list args;
va_start( args, format );
vsnprintf( szMsg, sizeof(szMsg) - 1, format, args );
va_end(args);
// code to print szMsg to a file or whatever here
}
これをスタンドアロン関数ではなくクラスとして使用する場合は、ロガー演算子()をオーバーロードすることができ、同じように機能します