我一直在寻找一种解决方案来同时写入文件和控制台。我找到了一个很好的解决方案here。
当我在C ++ 11之前工作时,我不得不对Orbit中Lightness Races的代码进行一些小改动:
#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
,但我仍然想知道这里发生了什么以及如何解决它。
答案 0 :(得分:12)
struct OutputAndConsole : std::ofstream
{
// ...
};
template <typename T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);
正如其他人所提到的,std::endl
是一个模板功能。模板函数不是值,它只是一个名称。
如果您尝试将模板函数传递给期望兼容签名功能的函数,则可以将模板函数转换为值。如果传递给采用T
或const T&
的模板函数,则不转换为值,因为模板函数的名称代表整个主机可能的价值观。
由于std::endl
不是自定义编写的operator<<
的有效参数,因此它会查找其他地方。它找到std::ofstream
&#39; s operator<<
通过显式函数指针获取io操纵器函数。
那个有效,它可以将endl
转换为该函数指针类型!所以,兴高采烈地称之为。
要解决此问题,请向operator<<
添加OutputAndConsole
重载,该重载需要io操纵器函数指针。
最简单的方法是编写辅助函数:
template <class T>
void output_to(OutputAndConsole& strm, const T& var)
{
std::cout << var;
static_cast<std::ofstream&>(strm) << var;
};
然后两个<<
重载:
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 :(得分: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;
时,编译器会从std::endl
的{{1}}和CharT
实例化Traits
。
在您的代码中,编译器使用 decltype(std::cout)
中的CharT
和Traits
实例化模板,因为std::ofstream
是OutputAndConsole
的后代}。当std::ofstream
尝试输出错误的std::cout
实例时,它会失败。
PS:最后一段只是部分正确。当你写
std::endl
其中oac << something;
的类型为T,
something
第一个定义是可能的,因为std::ofstream& std::ofstream::operator<<(T) // or operator<<(const T&)
// -- or --
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);
从OutputAndConsole
继承了成员函数operator<<
。第二种形式由您提供。
当std::ofstream
是变量时,它使用第二个定义。
当something
是模板时,它不能使用第二个定义,因为无法确定模板的参数。所以它使用了第一个定义。因此,
something
相当于
oac << std::endl; // std::endl is a template
我们可以通过以下代码看到它:
static_cast<ofstream&>(oac) << std::endl;
此代码不会打印“X”。
答案 2 :(得分:5)
std::endl
是一个函数,而不是字符串。
你的重载方法需要一个字符串来进行重载,所以当你执行<< std::endl
您需要创建一个运算符,该运算符使用与std:endl
具有相同签名的函数来执行重载。
std::ostream& operator<<( std::ostream& (*f)(std::ostream&) )
答案 3 :(得分:4)
我建议不要通过流接口实现标准I / O流功能,而是使用streambuffer接口。只要流被识别为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
。
答案 4 :(得分: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;
}