Что может пойти не так, если cout.rdbuf() используется для переключения буфера и никогда не устанавливает его обратно?

Автор представил этот код под заголовком A bus error on my platform

#include <fstream>
#include <iostream>

int main()
{
    std::ofstream log("oops.log");
    std::cout.rdbuf(log.rdbuf());
    std::cout << "Oops!\n";
    return 0;
}

Строка "Oops!\n" печатается в файл "oops.log". Код не восстанавливает streambuf cout, но VS2010 не сообщает об ошибке времени выполнения.


person John Kalane    schedule 13.02.2013    source источник
comment
Вы должны подробнее остановиться на своем заголовке.   -  person 0x499602D2    schedule 14.02.2013


Ответы (3)


Поскольку log и std::cout совместно используют буфер, этот буфер, вероятно, будет освобожден дважды (один раз, когда log выйдет из области видимости, а затем еще раз, когда программа завершится).

Это приводит к неопределенному поведению, поэтому трудно сказать точную причину, по которой он вызывает ошибку шины на его машине, а на вашей — молча.

person Frédéric Hamidi    schedule 13.02.2013
comment
Есть ли способ обойти это? - person Caesar; 13.02.2013
comment
@ Цезарь, да, в первую очередь не разделять буферы потока. Интересно, чего этим пытался добиться первоначальный автор. - person Frédéric Hamidi; 13.02.2013

Поскольку в других ответах не упоминается, что с этим делать, я приведу это здесь. Вам нужно сохранить и восстановить буфер, которым должен управлять cout. Например:

#include <fstream>
#include <iostream>

// RAII method of restoring a buffer
struct buffer_restorer {
    std::ios &m_s;
    std::streambuf *m_buf;

    buffer_restorer(std::ios &s, std::streambuf *buf) : m_s(s), m_buf(buf) {}
    ~buffer_restorer() { m_s.rdbuf(m_buf); }
};

int main()
{
    std::ofstream log("oops.log");
    buffer_restorer r(std::cout, std::cout.rdbuf(log.rdbuf()));
    std::cout << "Oops!\n";
    return 0;
}

Теперь, когда буфер cout заменяется до того, как cout будет уничтожен в конце программы, поэтому, когда cout уничтожает свой буфер, происходит правильное действие.


Для простого перенаправления стандартного ввода-вывода обычно среда уже имеет возможность сделать это за вас (например, перенаправление ввода-вывода в оболочке). Вместо приведенного выше кода я бы, вероятно, просто запустил программу как:

yourprogram > oops.log

Также следует помнить, что std::cout — это глобальная переменная со всеми теми же недостатками, что и другие глобальные переменные. Вместо того, чтобы изменять или даже использовать его, вы можете предпочесть использовать обычные методы, чтобы избежать глобальных переменных вообще. Например, вы можете передать параметр std::ostream &log_output и использовать его вместо того, чтобы код использовал cout напрямую.

person bames53    schedule 13.02.2013

Ваша программа имеет неопределенное поведение.

Деструктор глобального объекта cout удалит буфер потока при выходе за пределы области видимости, и то же самое верно для log, которому также принадлежит тот самый буфер потока. Таким образом, вы дважды удаляете один и тот же объект.

Когда программа имеет неопределенное поведение, может произойти что угодно, от форматирования жесткого диска до завершения работы без каких-либо ошибок.

Например, на моей платформе программа входит в бесконечный цикл после возврата из main().

person Andy Prowl    schedule 13.02.2013