簡體   English   中英

當 std::ostream object 被 std::flush 時,如何插入換行符?

[英]How can I inject a newline when a std::ostream object is std::flush'ed?

我如何最低限度地包裝std::ofstream以便對sync ( std::flush ) 的任何調用都變成對std::endl的調用。

(以下所有內容都是對“你為什么要那樣做?”問題的回答,與上述問題無關)


我有一個包含自定義std::ostream實現(環繞 zmq)的應用程序,我們稱它為zmq_stream

這個zmq_stream也在內部實現了一個自定義的streambuffer ,並且工作得很好。

這種設計的動機之一是在我的單元測試中,我可以簡單地將單元測試中的zmq_stream替換為常規的舊std::ostringstream並測試結果,完全隱藏 .network 層。

應用程序本身的功能之一是通過命令行標志將應用程序 output 重定向到文件或標准輸出。

再一次,在啟動時使用基本開關,我可以將zmq_streamstd::ofstream或普通的舊std::cout傳遞到我的主循環。

這是完成這樣的事情:

std::ostream* out;
switch(cond)
{
  case 1:
    out = &std::cout;
    break;
  case 2:
    out = new ofstream("file");
    break;
  case 3:
    out = new zmq_stream(method, endpoints);
    break;
}

main_loop(out);

zmq_stream的用法如下:


zmq_stream out(method, endpoints);

out << "do something " << 42 << " or other";
out << "and another" << std::flush; // <- full constructed buffer is sent over the network

注意:根據設計,我在刷新到 .network 時使用std::flush而不是std::endl 我不希望 append 換行到所有 my.network 傳輸。 因此,應用程序的 all.network 輸出使用std::flush而不是std::endl並且更改它不是正確的答案。

問題:出於測試目的,一切都按預期工作,對於std::coutstd::ofstream選項,我希望能夠在調用std::flush插入換行符,以便 my.network 傳輸可以在stdout上分隔成換行符。 這將允許我將它們 pipe 添加到標准的 posix 工具套件中......

沒有這種注入,就沒有辦法確定(除了計時)網絡邊界在哪里。

我在這里尋找盡可能少的覆蓋,這樣我就不必寫出一堆新類。 理想情況下,重寫sync()虛方法以便它調用overflow('\n'); 然后調用 base sync()

然而,事實證明這比我預期的更具挑戰性,所以我想知道我是否做錯了。

我如何包裝文件 stream.

您可以編寫一個<<重載來攔截std::flush並添加\n 我只是概述了這個想法,沒有生產就緒代碼。

#include <iostream>

struct foo {
    std::ostream& out = std::cout;

    template <class T> 
    foo& operator<<(const T& t) {
        out << t;
        return *this;
    }

    foo& operator<<(std::ostream& (*f)(std::ostream&)) {
        if (f == &std::flush<std::ostream::char_type,std::ostream::traits_type>) { out << "\n"; }
        out << f;
        return *this;
    }
};


int main() {
    foo f;
    f << "a"  << std::endl << "b" << std::flush << "c";
}

在 output 中,您可以看到現在std::endlstd::flush都添加了一個\n std::endl因為它無論如何都這樣做了,而std::flush因為包裝器添加了它。 現場演示

a
b
c

通常參考std function 應該小心。 僅當它們是顯式可尋址函數時才允許。 實際上我沒有發現提到過,但是將它們作為 function 指針傳遞是 io-manipulators 應該如何使用。

我寫的初始解決方案不起作用,因為它是一個編譯時解決方案,如果不重新實現 inheritance 就無法打開它(這樣main_loop function 得到一個基數 class 作為參數)。

相反,這是原始問題的答案:

#include <iostream>
#include <fstream>
#include <iomanip>

typedef std::ostringstream zmq_stream;

template <class T>
class newline_injector_streambuf: public std::basic_streambuf<T> {
public:
    using int_type = typename std::basic_streambuf<T>::int_type;
    newline_injector_streambuf(std::basic_streambuf<T>& dest) : sink(dest) {}

protected:

    virtual int_type sync() override { overflow('\n'); return sink.pubsync(); }
    virtual int_type overflow(int_type c) override { return sink.sputc(c); }

    std::basic_streambuf<T>& sink;
};

template <class T>
struct newline_injector_stream : public std::basic_ostream<typename T::char_type> {
    newline_injector_streambuf<typename T::char_type> buf;
    newline_injector_stream(T* file) : buf(*file->rdbuf())
    {
        std::basic_ostream<typename T::char_type>::rdbuf(&buf);
    }
};

void test(std::ostream& out)
{
    out << std::setfill('x') << std::setw(10) << "" << std::flush;
    out << "Hello, world!" << std::flush << "asdf" << std::endl;
}

int main() {

    newline_injector_stream out1(&std::cout);
    newline_injector_stream out2(new std::ofstream("test.output", std::ios::out | std::ios::ate));

    test(std::cout);
    test(out1);
    test(out2);

    return 0;
}



這是由於 @463035818-is-not-a-number 的初始答案而設計的解決方案:

    struct zmq_stream;
    template<typename T>
    struct output_device {
        T& output;

        output_device(T& backing_stream) : output(backing_stream){}

        template <typename U>
        output_device& operator<<(const U& u)
        {
            output << u;
            return *this;
        }

        output_device& operator<<(std::ostream& (*f)(std::ostream&)) {

          if constexpr (!std::is_same<T, zmq_stream>::value)
            if (f == &std::flush<std::ostream::char_type,std::ostream::traits_type>)
                output << "\n";
            output << f;
            return *this;
        }
    };

// Usage:

int main()
{
   /* prelude code */
   auto out1 = output_device(std::cout);
   auto out2 = output_device(std::ofstream("filename"));
   auto out3 = output_device(zmq_stream(method, endpoints));

   out1 << "foo" << std::flush << "bar"; // results in foo\nbar
   out2 << "foo" << std::flush << "bar"; // results in foo\nbar
   out3 << "foo" << std::flush << "bar"; // results in foobar

}

template <class T>
class separator: public std::basic_streambuf<T> {
public:
    using int_type = typename std::basic_streambuf<T>::int_type;
    using char_type = typename std::basic_streambuf<T>::char_type;
    using traits_type = typename std::basic_streambuf<T>::traits_type;

    separator(std::basic_streambuf<T> *dest) : sink(dest) {}
protected:

    virtual int_type sync() override {
        if (nullptr == sink)
            return 1;
        //std::basic_streambuf<T>::overflow('\n');
        overflow('\n');
        return sink->pubsync();
    }

    virtual int_type overflow(int_type c) override {
        if (sink == nullptr)
            return 1;
        return sink->sputc(c);
    }

    std::basic_streambuf<T> *sink = nullptr;
};

template <class T> struct newline_injector_stream : public std::basic_ostream<T> {
    separator<T> buf;
public:
    newline_injector_stream(std::basic_ostream<T> &file) : buf(file.rdbuf())
    {
        std::basic_ostream<T>::rdbuf(&buf);
    }
};

main_loop function 簽名現在從采用std::ostream*類型變為output_device&類型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM