Имеется такой простой интерфейсик:
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 внимания не обращайте, это просто что бы кратко набросать конкретно эту проблему и что бы все компилировалось.
On Mon, 10 Oct 2005 22:43:12 +0400, _Winnie <23256@users.rsdn.ru> wrote:
[]
> ЧТО ЖЕ ДЕЛАТЬ?
Пересмотреть дизайн.
Возможно, expression templates.
--
Maxim YegorushkinPosted via RSDN NNTP Server 1.9
Здравствуйте, 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
Здравствуйте, _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;
}
Здравствуйте, _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>>
Здравствуйте, srggal, Вы писали:
S>Когда-то я писал свой логгер, примерно по такой схеме, при Вашем желании, могу как-нить бросить его на мыло.
S>Единственное, я буфферизировал в ostring_stream, а потом его выводил в деструкторе, такой подход дает несколько плюсов, один из которых наличие соответсвкющих операторов вывода STL
ostringstream естественно (рука-злодейка
)
... << RSDN@Home 1.1.4 stable rev. 510>>