问题
With glibc's stdio, I can swap a memstream for stdout, thereby capturing the output of a piece of code compiled to output to stdout:
#include <stdio.h>
void swapfiles(FILE* f0, FILE* f1){ FILE tmp; tmp = *f0; *f0 = *f1; *f1 = tmp; }
void hw_c(){ puts("hello c world"); }
int c_capt(){
FILE* my_memstream;
char* buf = NULL;
size_t bufsiz = 0;
if( (my_memstream = open_memstream(&buf, &bufsiz)) == NULL) return 1;
FILE * oldstdout = stdout;
swapfiles(stdout, my_memstream);
hw_c();
swapfiles(stdout, my_memstream);
fclose(my_memstream);
printf("Captured: %s\n", buf);
}
I'm curious if the same is possible for iostreams
.
My naive attempt won't compile:
#include <iostream>
#include <string>
#include <sstream>
void hw_cc(){ std::cout<<"hello c++ world\n"; }
int cc_capt(){
using namespace std;
stringstream ss;
string capt;
//std::swap(ss,cout); //<- the compiler doesn't like this
hw_cc();
//std::swap(ss,cout);
cout<<"Captured: "<<capt<<'\n';
}
int main(int argc, char** argv){
c_capt();
puts("---------------------------------");
cc_capt();
return 0;
}
回答1:
You can, but you don't swap the whole stream--just the stream buffer.
void cc_capt() {
using namespace std;
stringstream ss;
auto orig = std::cout.rdbuf(ss.rdbuf());
hw_cc();
std::cout.rdbuf(orig);
std::cout << "captured: " << ss.str() << "\n";
}
Note that in this case, we're not really using the stringstream
itself at all, just the stringbuf
it contains. If we wanted, we could just define a basic_stringbuf<char>
and use that directly instead of defining a stringstream
and then only use the stringbuf
it contains.
回答2:
Based on Jerry's samples, I wrote a template which has one great advantage, it's safe (i.e. if an exception occurs, your buffer gets restored automatically.)
Use this way:
{
ostream_to_buf<char> buf(std::cout);
... run code which `std::cout << "data"` ...
std::string const output(buf.str());
... do something with `output` ...
} // <-- here the buffer is restored
Here is the functional template which I think follows the STL pretty closely. The template is itself an std::stringbuf
which inserts itself in the constructor. The destructor restores the original buffer so it is exception safe.
template<
class CharT
, class Traits = std::char_traits<CharT>
, class Allocator = std::allocator<CharT>
>
class ostream_to_buf
: public std::basic_stringbuf<CharT, Traits, Allocator>
{
public:
typedef CharT char_type;
typedef Traits traits_type;
typedef typename Traits::int_type int_type;
typedef typename Traits::pos_type pos_type;
typedef typename Traits::off_type off_type;
typedef Allocator allocator_type;
typedef std::basic_stringbuf<char_type, traits_type, allocator_type> stringbuf_type;
typedef std::basic_ostream<char_type, traits_type> stream_type;
typedef std::basic_streambuf<char_type, traits_type> streambuf_type;
typedef std::basic_string<char_type, traits_type, allocator_type> string_type;
ostream_to_buf<char_type, traits_type, allocator_type>(stream_type & out)
: f_stream(out)
, f_original(f_stream.rdbuf(this))
{
}
ostream_to_buf<char_type, traits_type, allocator_type>(ostream_to_buf<char_type, traits_type, allocator_type> const & rhs) = delete;
ostream_to_buf<char_type, traits_type, allocator_type> & operator = (ostream_to_buf<char_type, traits_type, allocator_type> const & rhs) = delete;
~ostream_to_buf()
{
f_stream.rdbuf(f_original);
}
private:
stream_type & f_stream;
streambuf_type * f_original = nullptr;
};
The copy constructor and assignment operator are deleted because these do not work in this case.
You probably can make it work with C++11 or even C++03. I have C++14 but I don't think any of these require C++14.
来源:https://stackoverflow.com/questions/37758378/swapping-a-stringstream-for-cout