буферизация и exception safety.
От: _Winnie Россия C++.freerun
Дата: 10.10.05 18:43
Оценка:
Имеется такой простой интерфейсик:

struct text_out_t
{
public:
    virtual void put(char c) const = 0;
    virtual ~text_out_t() {}
};

//пользователь как-то определеляет свои операторы <<
const  text_out_t &operator <<(const text_out_t &to, int i)
{
    to.put('0' + i%10); //только первую цифру, но это просто пример.
    return to;
}

//другой пользователь как-то определеляет своих потомков text_out_t

struct file_text_out_t: public text_out_t
{
    FILE *m_out_file;

    file_text_out_t(FILE *in_in_file)
        :m_out_file(in_in_file)
    {        
    }

    void put(char c) const 
    {
        if (-1 == putc(c, m_out_file))
            throw std::runtime_error("write operation failed");
    }
};

//да, возврат по значению, так и должно быть.
file_text_out_t text_out(FILE *f)
{
    return file_text_out_t(f);
}


//третий пользователь это всё как-то юзает.
int main()
{
    FILE *file = stdout;
    text_out(file) << 1 << 2 << 3;
}



Теперь предположим, что профайлер показал, что виртуальная функция на каждый символ — это слишком жирно.
Можно делать буферизацию, типа такой:

struct text_out_t
{
protected:
    mutable char buf[16];
    mutable int top;

    virtual void put_batch() const = 0;
    text_out_t()
        :top(0)
    {
    }
public:
    // теперь inline, а не виртуальтная.
    void put(char c) const
    {
        if (top == 16)
            put_batch();
        buf[top++] = c;
    }

    virtual ~text_out_t() {}
};


//другой пользователь как-то определеляет своих потомков text_out_t
struct file_text_out_t: public text_out_t
{
    FILE *m_out_file;

    file_text_out_t(FILE *in_in_file)
        :m_out_file(in_in_file)
    {        
    }

    virtual void put_batch() const
    {
        if (text_out_t::top > fwrite(text_out_t::buf, 1, text_out_t::top, m_out_file))
            throw std::runtime_error("write operation failed");
        text_out_t::top = 0;
    }

    //!!!!!! Вот здесь и проблема! исключение в деструкторе !
    ~file_text_out_t()
    {
        try
        {
            put_batch();
        }
        catch (...) //ошибка записи в файл.
        {
            //и что?
        }
    }

};


Исключение в деструкторе — зло. (Подробности у Саттера/Страуструпа).

Заставлять пользователя писать что-то вроде
text_out(file) << 1 << 2 << 3 <<flush;

(где исключения будет бросаться не в деструкторе, а в последнем operator <<flush) нельзя. Бесмыссленно, неудобно, будут забывать, и т.д.

проверять uncaugh_exception в деструкторе опять же бесмысленно, см. Саттера. Это прямой путь к memory leaks.

Игнорировать ошибки вывода и исключения нельзя.

ЧТО ЖЕ ДЕЛАТЬ?

PS. на const/mutable внимания не обращайте, это просто что бы кратко набросать конкретно эту проблему и что бы все компилировалось.
Правильно работающая программа — просто частный случай Undefined Behavior
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.