2016-06-28 11 views
27

私は、ファイルとコンソールに同時に書き込むソリューションを探していました。私は良い解決策を見つけたhereなぜ `<< std :: endl`が呼び出したい演算子を呼び出さないのですか?

私は事前C++ 11を働いているとして、私は、軌道上での明度レースからコードに小さな変更をしなければならなかった:

#include <iostream> 
#include <fstream> 
#include <string> 

struct OutputAndConsole : std::ofstream 
{ 
    OutputAndConsole(const std::string& fileName) 
     : std::ofstream(fileName.c_str()) // constructor taking a string is C++11 
     , fileName(fileName) 
    {}; 

    const std::string fileName; 
}; 

template <typename T> 
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var) 
{ 
    std::cout << var;  
    static_cast<std::ofstream&>(strm) << var; 
    return strm; 
}; 

それはパズル私を取る離れて小さなことからうまく動作します。私はこのようにそれを使用する場合:

int main(){ 
    OutputAndConsole oac("testLog.dat"); 
    double x = 5.0; 
    oac << std::endl; 
    static_cast<OutputAndConsole&>(oac << "foo \n" << x << "foo").operator<<(std::endl); 
    oac << "foo" << std::endl; 
} 

彼らはファイルに正しく表示されている間、すべてのstd::endlがコンソールに出力するために無視されます。私の推測では、std::endlを使用すると、ostream::operator<<が呼び出され、ファイルに出力されますがコンソールには出力されません。 static_cast<OutputAndConsole&>の行は、正しい演算子を呼び出すための私の厄介な試みですが、コンソールには\nからの改行だけが表示されます。

std::endlなぜ間違った演算子が呼び出されますか?

どのようにして正しいものを呼び出すことができますか?

PS:私は、私は問題なく\nを使用することができることを知っているが、それでも私はここに行くとどのようにそれを修正することです何を知っていただきたいと思います。

+1

''\ n' 'を使用して行を終了すると、この問題は発生しません。 'std :: endl'が行う余分なものが本当に必要ですか? –

+1

@PeteBeckerあなたは絶対に正しいですが、私はそれを必要としませんが、何が間違っているのか、それを修正する方法を理解したいと思います。 – user463035818

+3

停止。 'ostream'には既にそれらを扱う演算子があります。その仕事をして、フォーマットされた出力を処理させてください。 2つのストリームで生の出力を複製するだけで済みます。これは 'streambuf'クラスの仕事です。それを正しく実装し、すべてのオペレーションを他の2つのバッファにリダイレクトしてください:[Dr.Dobbの記事](http://www.drdobbs.com/questions-answers/184403387)、[別のリンク](http://www.cplusplus。 com/forum/general/64174 /#msg347154) –

答えて

12
struct OutputAndConsole : std::ofstream 
{ 
    // ... 
}; 

template <typename T> 
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var); 

他にも言及したように、std::endlはテンプレート関数である。テンプレート関数は値ではなく、単なる名前です。

互換性のある署名の関数を期待する関数に渡すと、テンプレート関数を値に変換できます。 は、Tまたはconst T&というテンプレート関数に渡された場合、値に変換されません。テンプレート関数の名前は、ホストの可能な値を表しているためです。

std::endlは、カスタム書込みoperator<<の有効な引数ではないため、別の場所にあります。明示的な関数ポインタによってioマニピュレータ関数をとるstd::ofstreamoperator<<が見つかりました。

それが動作すると、endlをその関数のポインタ型に変換できます!だから、嬉しいことに、それはそれを呼びます。

この問題を解決するには、ioマニピュレータの関数ポインタを使用するOutputAndConsoleoperator<<オーバーロードを追加します。

template <class T> 
void output_to(OutputAndConsole& strm, const T& var) 
{ 
    std::cout << var;  
    static_cast<std::ofstream&>(strm) << var; 
}; 

2つの<<オーバーロード::

template<class T> 
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var) { 
    output_to(strm, var); 
    return strm; 
} 
OutputAndConsole& operator<<(OutputAndConsole& strm, std::ostream& (*var)(std::ostream&)) { 
    output_to(strm, var); 
    return strm; 
} 

std::endlテンプレートが一致<<を見つけることが原因

それを行うための最も簡単な方法は、ヘルパー関数を記述することです。

+1

私はそれを受け入れます(正直なところ私は誰が最初か誰かを気にしません)。実際には2つの質問を1つに持つのは間違いでした...ごめんなさい – user463035818

5

std::endlは文字列ではなく、関数です。それはあなたがあなたのオーバーロードを行うためにstd:endlと同じシグネチャを持つ関数を取る演算子を作成する必要が<< std::endl

を行う際に呼び出されます、このいずれかをイマイチよう あなたのオーバーロードされたメソッドは、オーバーロードのための文字列を取ります。

std::ostream& operator<<(std::ostream& (*f)(std::ostream&)) 
+0

私は本当に理解できません、私のオーバーロードされた演算子は 'const T&'文字列ではありません – user463035818

+0

@ tobi303ええ、const T、文字列ではありません。それでも、std:endlに関しては、彼に全く同じシグネチャを与える必要があります。私は演算子で使用されるstd :: endlの署名で私の答えを編集しました。 – Mekap

+0

それはうまくいきましたが、まだテンプレートでカバーされていなかった理由は分かりません。 std :: ostream&(*)(std :: ostream&) 'が有効なテンプレートパラメータであることを期待していますか?いいえ、私は' const T& 'の代わりに' T'を試しました。 – user463035818

11

私たちは単純な何かを試してみましょう:

#include <iostream> 

struct Foo { }; 

template <typename T> 
Foo& operator<<(Foo& foo, const T& var) 
{ 
    std::cout << var; 
    return foo; 
}; 

int main(){ 
    Foo foo; 
    foo << std::endl; 
} 

これはコンパイルされません:

a1.cpp: In function ‘int main()’: 
a1.cpp:14:9: error: no match for ‘operator<<’ (operand types are ‘Foo’ and ‘<unresolved overloaded function type>’) 
    foo << std::endl; 
     ^
a1.cpp:14:9: note: candidates are: 
a1.cpp:6:6: note: template<class T> Foo& operator<<(Foo&, const T&) 
Foo& operator<<(Foo& foo, const T& var) 
    ^
a1.cpp:6:6: note: template argument deduction/substitution failed: 
a1.cpp:14:17: note: couldn't deduce template parameter ‘T’ 

なぜ?コンパイラは私たちに何を伝えようとしていますか?

のstd :: ENDLの定義は、ここで見つけることができます: http://en.cppreference.com/w/cpp/io/manip/endl

template< class CharT, class Traits > 
std::basic_ostream<CharT, Traits>& endl(std::basic_ostream<CharT, Traits>& os); 

だからstd::endlが変数ではありません。 テンプレートです。より正確には、テンプレート関数。私の小さなコードでは、コンパイラはテンプレートをインスタンス化することができません。

我々は直接std::cout << std::endl;を呼び出すと、コンパイラはCharTdecltype(std::cout)Traitsからstd::endlをインスタンス化します。あなたのコードで

あなたOutputAndConsolestd::ofstreamの子孫であるため、コンパイラは代わりに、std::ofstreamからCharTTraitsを使用してテンプレートをインスタンス化します。 std::coutstd::endlという誤ったインスタンス化を出力しようとすると失敗します。

PS:最後の段落は部分的に正しいだけです。あなたはsomethingはT型を持つ

oac << something; 

を書くときOutputAndConsolestd::ofstreamからメンバ関数operator<<を継承したので、それは、理論的には、最初の定義が可能である2

std::ofstream& std::ofstream::operator<<(T) // or operator<<(const T&) 
// -- or -- 
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var); 

のいずれかを呼び出すことができます。 2番目のフォームはあなたから提供されます。

somethingが変数の場合、2番目の定義が使用されます。

somethingがテンプレートの場合、テンプレートのパラメータを決定する方法がないため、2番目の定義を使用できません。したがって、最初の定義が使用されます。したがって、

oac << std::endl; // std::endl is a template 

static_cast<ofstream&>(oac) << std::endl; 

と等価である私たちは、次のコードでそれを見ることができます。

#include <iostream> 

struct Foo : std::ofstream {}; 

template <typename T> 
Foo& operator<<(Foo& strm, const T& var) 
{ 
    std::cout << "X" << std::endl; 
    return strm; 
}; 

int main() { 
    Foo oac; 
    oac << std::endl; 
} 

このコードは、 "X" を印刷しません。

+0

私はそれについて考える必要がありますが、それは良い説明のように聞こえます – user463035818

3

私はマニピュレータの私自身のセットを作成し、それを動作させるには:

struct ManipEndl {}; 
const ManipEndl manip_endl; 
OutputAndConsole& operator<<(OutputAndConsole& strm, const ManipEndl& foo) 
{ 
    std::cout << std::endl;  
    static_cast<std::ofstream&>(strm) << '\n'; // no need for std::endl here 
    return strm; 
}; 

int main(){ 
    OutputAndConsole oac("testLog.dat"); 
    double x = 5.0; 
    oac << manip_endl; 
    oac << x << manip_endl << "foo" << manip_endl; 
    oac << "foo" << manip_endl; 
} 
+0

私は最初の答えで呼び出される理由を答えました。この回答は、それを修正する方法です。 'std :: endl'と改行は2つの異なることに注意してください。 'x << std :: endl'は' x << 'と同等です。\ n' << std :: flush' – user31264

+0

haha​​は2つの異なる答えを書くことができません;) – user463035818

4

は、私は、標準ストリームインタフェースを介してI/Oストリームの機能が、ストリームバッファインタフェースを実装していないお勧めします。カスタマイズされたストリームインタフェースは通常、ストリームがstd :: istream/std :: ostreamとして認識されると直ちに問題になります(あるオペレータ、マニフェクタ、または関数がストリームを参照するため)。

あなたが利用することができる:

#include <array> 
#include <iostream> 
#include <sstream> 
#include <vector> 

// BasicMultiStreamBuffer 
// ============================================================================ 

/// A (string) stream buffer for synchronizing writes into multiple attached buffers. 
template<class Char, class Traits = std::char_traits<Char>, class Allocator = std::allocator<Char> > 
class BasicMultiStreamBuffer : public std::basic_stringbuf<Char, Traits, Allocator> 
{ 
    // Types 
    // ===== 

    private: 
    typedef typename std::basic_stringbuf<Char, Traits> Base; 

    public: 
    typedef typename std::basic_streambuf<Char, Traits> buffer_type; 
    typedef typename buffer_type::char_type char_type; 
    typedef typename buffer_type::traits_type traits_type; 
    typedef typename buffer_type::int_type int_type; 
    typedef typename buffer_type::pos_type pos_type; 
    typedef typename buffer_type::off_type off_type; 

    private: 
    typedef typename std::vector<buffer_type*> container_type; 

    public: 
    typedef typename container_type::size_type size_type; 
    typedef typename container_type::value_type value_type; 
    typedef typename container_type::reference reference; 
    typedef typename container_type::const_reference const_reference; 
    typedef typename container_type::iterator iterator; 
    typedef typename container_type::const_iterator const_iterator; 


    // Construction/Destructiion 
    // ========================= 

    public: 
    BasicMultiStreamBuffer() 
    {} 

    template <typename...Buffers> 
    BasicMultiStreamBuffer(Buffers* ...buffers) { 
     std::array<buffer_type*, sizeof...(Buffers)> buffer_array{buffers...}; 
     m_buffers.reserve(buffer_array.size()); 
     for(auto b : buffer_array) { 
      if(b) 
       m_buffers.push_back(b); 
     } 
    } 

    template <typename Iterator> 
    BasicMultiStreamBuffer(Iterator first, Iterator last) 
    : m_buffers(first, last) 
    {} 

    ~BasicMultiStreamBuffer() { 
     sync(); 
    } 


    private: 
    BasicMultiStreamBuffer(BasicMultiStreamBuffer const&); // No Copy. 
    BasicMultiStreamBuffer& operator=(BasicMultiStreamBuffer const&); // No Copy. 


    // Capacity 
    // ======== 

    public: 
    bool empty() const { return m_buffers.empty(); } 
    size_type size() const { return m_buffers.size(); } 


    // Iterator 
    // ======== 

    public: 
    iterator begin() { return m_buffers.begin(); } 
    const_iterator begin() const { return m_buffers.end(); } 
    iterator end() { return m_buffers.end(); } 
    const_iterator end() const { return m_buffers.end(); } 


    // Modifiers 
    // ========= 

    public: 
    /// Attach a buffer. 
    void insert(buffer_type* buffer) { 
     if(buffer) m_buffers.push_back(buffer); 
    } 

    /// Synchronize and detach a buffer. 
    void erase(buffer_type* buffer) { 
     iterator pos = this->begin(); 
     for(; pos != this->end(); ++pos) { 
      if(*pos == buffer) { 
       char_type* p = this->pbase(); 
       std::streamsize n = this->pptr() - p; 
       if(n) 
        sync_buffer(*pos, p, n); 
       m_buffers.erase(pos); 
       break; 
      } 
     } 
    } 


    // Synchronization 
    // =============== 

    private: 
    int sync_buffer(buffer_type* buffer, char_type* p, std::streamsize n) { 
     int result = 0; 
     std::streamoff offset = 0; 
     while(offset < n) { 
      int k = buffer->sputn(p + offset, n - offset); 
      if(0 <= k) offset += k; 
      else { 
       result = -1; 
       break; 
      } 
      if(buffer->pubsync() == -1) 
       result = -1; 
     } 
     return result; 
    } 

    protected: 
    /// Synchronize with the attached buffers. 
    /// \ATTENTION If an attached buffer fails to synchronize, it gets detached. 
    virtual int sync() override { 
     int result = 0; 
     if(! m_buffers.empty()) { 
      char_type* p = this->pbase(); 
      std::streamsize n = this->pptr() - p; 
      if(n) { 
       iterator pos = m_buffers.begin(); 
       while(pos != m_buffers.end()) { 
        if(0 <= sync_buffer(*pos, p, n)) ++pos; 
        else { 
         pos = m_buffers.erase(pos); 
         result = -1; 
        } 
       } 
      } 
     } 
     this->setp(this->pbase(), this->epptr()); 
     if(Base::sync() == -1) 
      result = -1; 
     return result; 
    } 

    private: 
    container_type m_buffers; 
}; 

typedef BasicMultiStreamBuffer<char> OStreamBuffers; 


// BasicMultiStream 
// ============================================================================ 

template<class Char, class Traits = std::char_traits<Char>, class Allocator = std::allocator<Char> > 
class BasicMultiStream : public std::basic_ostream<Char, Traits> 
{ 
    // Types 
    // ===== 

    private: 
    typedef std::basic_ostream<Char, Traits> Base; 

    public: 
    typedef BasicMultiStreamBuffer<Char, Traits, Allocator> multi_buffer; 
    typedef std::basic_ostream<Char, Traits> stream_type; 

    typedef typename multi_buffer::buffer_type buffer_type; 
    typedef typename multi_buffer::char_type char_type; 
    typedef typename multi_buffer::traits_type traits_type; 
    typedef typename multi_buffer::int_type int_type; 
    typedef typename multi_buffer::pos_type pos_type; 
    typedef typename multi_buffer::off_type off_type; 

    typedef typename multi_buffer::size_type size_type; 
    typedef typename multi_buffer::value_type value_type; 
    typedef typename multi_buffer::reference reference; 
    typedef typename multi_buffer::const_reference const_reference; 
    typedef typename multi_buffer::iterator iterator; 
    typedef typename multi_buffer::const_iterator const_iterator; 


    // Construction 
    // ============ 

    public: 
    BasicMultiStream() 
    : Base(&m_buffer) 
    {} 

    template <typename ...Streams> 
    BasicMultiStream(Streams& ...streams) 
    : Base(&m_buffer), m_buffer(streams.rdbuf()...) 
    {} 

    private: 
    BasicMultiStream(const BasicMultiStream&); // No copy. 
    const BasicMultiStream& operator = (const BasicMultiStream&); // No copy. 

    // Capacity 
    // ======== 

    public: 
    bool empty() const { return m_buffer.empty(); } 
    size_type size() const { return m_buffer.size(); } 


    // Iterator 
    // ======== 

    public: 
    iterator begin() { return m_buffer.begin(); } 
    const_iterator begin() const { return m_buffer.end(); } 
    iterator end() { return m_buffer.end(); } 
    const_iterator end() const { return m_buffer.end(); } 


    // Modifiers 
    // ========= 

    public: 
    template <typename StreamIterator> 
    void insert(StreamIterator& first, StreamIterator& last) 
    { 
     while(first != last) 
      insert(*first++); 
    } 
    void insert(stream_type& stream) { m_buffer.insert(stream.rdbuf()); } 
    void erase(stream_type& stream) { m_buffer.erase(stream.rdbuf()); } 

    private: 
    multi_buffer m_buffer; 
}; 

typedef BasicMultiStream<char> MultiStream; 


int main() { 
    MultiStream s(std::cout, std::cerr, std::clog); 
    s << "Hello World" << std::endl; 
    printf("[Three lines of output]\n"); 
} 

注意を、STD :: basic_streambufインターフェイスに変更を適用するだけで機能がvirtual int sync() overrideです。

標準の基本ストリームクラスは、カスタムストリームクラスの派生と初期化以外のインタフェースを提供しません。実際の(仮想)インターフェイスは標準ストリームバッファbasic_streambufです。

+1

ありがとうございます。しかし、これはコンソールとファイルに同時に書き込む方法を尋ねる質問に賛成する方が良いと思いますが、これは具体的にはコード内の過負荷に関するもので、なぜ 'std :: endl '。私は時間があれば、私は新しい質問をし、あなたはこのasnwerを動かすかもしれません。 – user463035818

関連する問題