буферизация и 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
Re: буферизация и exception safety.
От: MaximE Великобритания  
Дата: 10.10.05 18:56
Оценка:
On Mon, 10 Oct 2005 22:43:12 +0400, _Winnie <23256@users.rsdn.ru> wrote:

[]

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


Пересмотреть дизайн.
Возможно, expression templates.

--
Maxim Yegorushkin
Posted via RSDN NNTP Server 1.9
Re[2]: буферизация и exception safety.
От: _Winnie Россия C++.freerun
Дата: 10.10.05 21:12
Оценка:
Здравствуйте, MaximE, Вы писали:

ME>On Mon, 10 Oct 2005 22:43:12 +0400, _Winnie <23256@users.rsdn.ru> wrote:


ME>Пересмотреть дизайн.

Крайне нежелательно. Хочу удобства пользователю.

Можно конечно сделать что-то вроде file | text_out << 1 << 2 << 3, где выражение text_out << 1 << 2 << 3 запомнит все ссылки на локальные переменные в списке(как на http://www.teggo.com/wakka/CppFolder/Format?v=1c9d). Но это сложнее, выглядит неклассически, и потребует дальнейшего полиморфного вызова для распечатки запомнего объекта(expression templates не хочу из-за дикого времени компиляции). Хочется классического text_out(file) << 1 << 2 <<3

ME>--

ME>Maxim Yegorushkin
Правильно работающая программа — просто частный случай Undefined Behavior
Re[3]: буферизация и exception safety.
От: Dmi_3 Россия  
Дата: 10.10.05 21:36
Оценка:
Здравствуйте, _Winnie, Вы писали:

_W> Хочется классического text_out(file) << 1 << 2 <<3



class text_out{
  std::ostream& out;
public:
  text_out(std::ostream& File) : out(File) {}
  ~text_out(){out<<"done"<<std::endl<<std::flush;}
  text_out const& operator<<(int x) const { out<<x; return *this; }
};
int main(){
  text_out(std::cout) << 1 << 2 <<3;
  return 0;
}
Re: буферизация и exception safety.
От: srggal Украина  
Дата: 11.10.05 06:40
Оценка:
Здравствуйте, _Winnie, Вы писали:

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

Заблаговременно приношу свои извинения, поскольку код сырой до безобразия:

template< typename LoggingPolicy >
struct text_out_t
    :    public LoggingPolicy
{
    friend text_out_t<LoggingPolicy> text_out(FILE *f);

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()
     {
        put_batch( buf, top );
     }
};

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

    file_logging()
        :    m_out_file( NULL )
    {}


    text_out_t<file_logging>& Init( FILE *in_in_file )
    {
        m_out_file = in_in_file;

        return static_cast< text_out_t<file_logging>& >(*this);
    }

    bool put_batch( const char*const Buff, size_t length ) const
    {
         return length > fwrite(Buff, 1, length, m_out_file);
    }
};

template< typename TextOutT >
const  TextOutT& operator <<(const TextOutT& to, int i)
{
    to.put('0' + i%10); //только первую цифру, но это просто пример.
    return to;
}

//да, возврат по значению, так и должно быть.
text_out_t<file_logging> text_out(FILE *f)
{
    return text_out_t<file_logging>().Init(f);
}
void main()
{
    FILE *file = stdout;
    text_out(file) << 1 << 2 << 3;
}


Когда-то я писал свой логгер, примерно по такой схеме, при Вашем желании, могу как-нить бросить его на мыло.
Единственное, я буфферизировал в ostring_stream, а потом его выводил в деструкторе, такой подход дает несколько плюсов, один из которых наличие соответсвкющих операторов вывода STL
... << RSDN@Home 1.1.4 stable rev. 510>>
Re[2]: буферизация и exception safety.
От: srggal Украина  
Дата: 11.10.05 06:42
Оценка:
Здравствуйте, srggal, Вы писали:

S>Когда-то я писал свой логгер, примерно по такой схеме, при Вашем желании, могу как-нить бросить его на мыло.

S>Единственное, я буфферизировал в ostring_stream, а потом его выводил в деструкторе, такой подход дает несколько плюсов, один из которых наличие соответсвкющих операторов вывода STL

ostringstream естественно (рука-злодейка )
... << RSDN@Home 1.1.4 stable rev. 510>>
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.