[英]Why does `<< std::endl` not call the operator I want it to call?
我正在尋找一種同時寫入文件和控制台的解決方案。 我在這里找到了一個不錯的解決方案。
當我在 C++11 之前工作時,我不得不對 Lightness Races in Orbit 中的代碼進行一些小改動:
#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;
}
然后控制台上的 output 的所有std::endl
都將被忽略,而它們在文件中正確顯示。 我的猜測是,當我使用std::endl
時,會調用ostream::operator<<
它將打印到文件而不是控制台。 帶有static_cast<OutputAndConsole&>
的行是我嘗試調用正確運算符的業余嘗試,但控制台上仍然只有\n
的換行符。
為什么std::endl
調用了錯誤的運算符?
我怎樣才能調用正確的?
PS :我知道我可以毫無問題地使用\n
,但我仍然想知道這里發生了什么以及如何解決它。
struct OutputAndConsole : std::ofstream
{
// ...
};
template <typename T>
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);
正如其他人所提到的, std::endl
是一個模板函數。 模板函數不是值 ,它只是一個名稱。
如果您嘗試將模板函數傳遞給期望兼容簽名功能的函數,則可以將模板函數轉換為值。 如果傳遞給采用T
或const T&
的模板函數, 則不會將其轉換為值,因為模板函數的名稱表示可能值的整個主機 。
因為std::endl
不是自定義編寫的operator<<
的有效參數,所以它在其他地方查找。 它找到std::ofstream
的operator<<
,它通過顯式函數指針獲取io操縱器函數。
那個工作,它可以將endl
轉換為該函數指針類型! 所以,興高采烈地,它稱之為。
要解決此問題,請向OutputAndConsole
io操縱器函數指針的OutputAndConsole
添加一個operator<<
overload。
最簡單的方法是編寫輔助函數:
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
模板找到匹配的<<
。
讓我們嘗試一些更簡單的方法:
#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
從CharT
和Traits
的decltype(std::cout)
在您的代碼中,編譯器使用來自 std::ofstream
CharT
和Traits
來實例化模板,因為您的OutputAndConsole
是std::ofstream
的后代。當 std::cout
嘗試輸出std::endl
的錯誤實例時,它會失敗。
PS:最后一段只是部分正確。 當你寫作
oac << something;
something
有T型,
從理論上講,它可以稱為兩個中的任何一個
std::ofstream& std::ofstream::operator<<(T) // or operator<<(const T&)
// -- or --
OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var);
第一個定義是可能的,因為OutputAndConsole
從std::ofstream
繼承了成員函數operator<<
。 第二種形式由您提供。
當something
是變量時,它使用第二個定義。
當something
是模板時,它不能使用第二個定義,因為無法確定模板的參數。 所以它使用了第一個定義。 因此,
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”。
std::endl
是一個函數,而不是一個字符串。 你的重載方法需要一個字符串來進行重載,所以當你執行<< std::endl
時,它不會被調用
您需要創建一個運算符,該運算符接受與std:endl
具有相同簽名的函數來執行重載。
std::ostream& operator<<( std::ostream& (*f)(std::ostream&) )
我建議不要通過流接口實現標准的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
。
為了使它工作,我會創建自己的一組操縱器:
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;
}
我有一個類似的問題,並通過讓我的operator<<
function 成為這樣的朋友來解決它:
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>
friend OutputAndConsole& operator<<(OutputAndConsole& strm, const T& var)
{
std::cout << var;
std::ofstream::operator << (var); //*See note at end
return strm;
}
};
* 這是從派生的 class 方法/運算符調用基本 class 方法/運算符的首選語法。 雖然您的語法在大多數情況下都可以工作,但如果std::ofstream operator <<
在ofstream
中聲明為純虛擬,它將失敗。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.