Home

Advertisement

All things considered... - Streamable Any [entries|archive|friends|userinfo]
Qbit

[ userinfo | livejournal userinfo ]
[ archive | journal archive ]

Streamable Any [Oct. 8th, 2007|01:03 am]
Previous Entry Add to Memories Tell a Friend Next Entry

1. Что такое boost::any. Введение для самых маленьких :)

boost::any — это контейнер для 0 или 1 элементов, который может содержать разные типы, но не разрешает неявных преобразований между ними. При этом типы должны быть CopyConstructible, и не должны бросать исключения в дторах.

boost::any позволяет сделать вид, что все типы (удовлетворяющие перечисленным требованиям) входят в иерархию с одним корнем (как в .NET FCL). А именно, корнем этой „иерархии“ будет класс any::placeholder, как видно из реализации. Кстати, советую заглянуть в реализацию, она очень кратка, красива и поучительна :) Никаких там union'ов или, прасти 'оспади, void*'ов, чисто ООП и тэ́мплит программинг.

Чтобы прочувствовать общность между System.Object и boost::any, сравните два примера:


using System;
using System.Windows.Forms;

...

private void ButtonOnClick(Object sender, EventArgs args)
{
  Button btn = sender as Button;
  if (btn != null)
    btn.Text = "Click me!";
}

private void FormOnClick(Object sender, EventArgs args)
{
  try {
    Form frm = (Form) sender;
    frm.Text = "Close me!";
  }
  catch (System.InvalidCastException ex) {
    MessageBox.Show("Invalid cast exception", "Exception",
      MessageBoxButtons.OK, MessageBoxIcon.Error);
  }
}
// Если бы все компиляторы (по крайней мере, хоть один!)
// были в состоянии правильно делать ADL,
// то этой строки не понадобилось бы.
using boost::any_cast;

...

void ButtonOnClick(boost::any sender, EventArgs args)
{
  if (Button* const btn = any_cast<Button>(&sender))
    btn->SetText("Click me!");
}

void FormOnClick(boost::any sender, EventArgs args)
{
  try {
    Form frm( any_cast<Form>(sender) );
    frm.SetText("Close me!");
  }
  catch (boost::bad_any_cast const& ex) {
    ::MessageBoxA(0, "Invalid cast exception",
      "Exception", MB_ICONERROR);
  }
}

Первая функция в каждой колонке использует „небросающее“ приведение. Обратите внимание, как это делается в коде на C++ — в аргумент any_cast'а передаётся указатель, целевой тип остаётся прежним. Тут выпендрёжа ради переменная объявляется а заголовке условного оператора (как в for'е), и видна только в скоупе if'а. В реальном коде такое встречается редко, но может быть полезным, когда у вас цепочка if'ов, и вводить свою „одноразовую“ переменную для каждого типа лениво и вообще error prone. Во второй функции используется „бросающая“ форма приведения. В общем, поведение any_cast'а похоже на поведение плюсового dynamic_cast'а.

2. Зачем нужен streamable any, и как его реализовать

Было бы удобно, если бы такой контейнер умел бы выводиться в поток. Но boost::any слишком универсален, поэтому в библиотеке не реализована функция, сужающая область применения шаблона на streamable типы. В то время, как мне приходится хранить не произвольные типы, а только фундаментальные и строки. А выбирать тип каждый раз только для того, чтобы вывести его на консоль немного напряжно. Поэтому, быть может, удобнее будет самопальный класс Any (implemented in terms of boost::any), который может хранить лишь типы, для которых определён оператор вывода в поток. Тогда можно будет сделать так:

Utils::Any const a(3.1415);
std::cout << a << std::endl;

Решение я содрал из книжки Björn Karlsson “Beyond the C++ Standard Library: An Introduction to Boost”. Разве что добавил пару const и по пространствам раскидал. Вот как оно выглядит:

// Utils.h

#pragma once

#include <boost/any.hpp>

#include <iostream>

namespace Detail
{
  class Streamer
  {
  public:
    virtual void Print(std::ostream& ostr, boost::any const& a) const = 0;
    virtual Streamer* Clone() const = 0;
    virtual ~Streamer() {}
  };

  template <typename T>
  class StreamerImpl : public Streamer
  {
  public:
    void Print(std::ostream& ostr, boost::any const& a) const {
      ostr << *boost::any_cast<T>(&a);
    }
    Streamer* Clone() const {
      return new StreamerImpl<T>();
    }
  };
}

namespace Utils
{
  class Any
  {
  public:
    Any() : streamer_(0) {}
    template <typename T> Any(T const& value)
      : streamer_(new Detail::StreamerImpl<T>), o_(value) {}
    Any(Any const& a)
      : streamer_(a.streamer_ ? a.streamer_->Clone() : 0), o_(a.o_) {}
    template <typename T> Any& operator =(T const& r) {
      Any(r).swap(*this);
      return *this;
    }
    Any& operator=(Any const& r) {
      Any(r).swap(*this);
      return *this;
    }
    ~Any() { delete streamer_; }
  public:
    Any& swap(Any& r) {
      std::swap(streamer_, r.streamer_);
      std::swap(o_, r.o_);
      return *this;
    }
    friend std::ostream& operator <<(std::ostream& ostr, Any const& a) {
      if (a.streamer_) { a.streamer_->Print(ostr, a.o_); }
      return ostr;
    }
  private:
    boost::any o_;
    Detail::Streamer* streamer_;
  };
}

Щаз пример употребления слабаю:

// Test.cpp

#include "Utils.h"

#include <boost/lexical_cast.hpp>

#include <iostream>
#include <string>

class D
{
  double d_;
public:
  explicit D(double d) : d_(d) {}
  double Value() const { return d_; }
  friend std::ostream& operator << (std::ostream& ostr, D const& d) {
    return ostr << d.Value();
  }
};

int main()
{
  D const d(3.1415);
  Utils::Any const a(d);
  std::cout << a << std::endl;

  // Приведение к строке теперь доступно автоматически.
  std::string const str( boost::lexical_cast<std::string>(a) );
  std::cout << str << std::endl;

  return 0;
}

LinkReply

Comments:
[User Picture]From: [info]cyberzx
2007-10-07 09:29 pm (UTC)

(Link)

зачем юзать any, если у тебя ограниченный список типов? используй boost.variant. он требует динамического выделения памяти и очень легко стримится.
[User Picture]From: [info]bik_top
2007-10-07 10:48 pm (UTC)

(Link)

он требует динамического выделения памяти
Имелось в виду, «не требует»?

зачем юзать any, если у тебя ограниченный список типов?
Список ограниченный, но большой, порядка 10. Типы оперделяются на этапе выполнения (считываются из файла). Типы всякие, например, boost::rational<N, D>.

используй boost.variant.
Тут есть несколько соображений.

* Boost.Variant я когда-то разбирал, когда ещё маленький был :) Как его толково использовать я тогда, чесгря, ниасилил. Надо заново свежим взглядом глянуть. Помню, static_visitor ещё нормально пошёл, а вот на recursive_wrapper мозг уже пообещал взорваться.

* «Шаблонность» и «стековость», быть может, способны уменьшить время работы программы. Но наверняка в разы увеличат время её написания (об этом чуть позднее). Да и компиляция, уверен, станет длиться нехилое время.

* Я так понимаю, что можно решить, будто я характерный C++-извращенец, но это не так :) Если извраты не сильно далеко заходят, я этому только рад (хе-хе, шутка), но начиная с некоторого момента это начинает напрягать.

Тут есть ещё такая аналогия: Boost.Regexp и Boost.Xpressive. Первое — обычная библиотека regexp'ов, она примерно так же выглядела бы на любом языке. Второе — сугубо плюсовое достижение. Там всё statically checked и compile time. Короче, DSEL (domain-specific embedded langauage, afair) на базе ужасного синтаксиса C++. И от него реально тошнит. Похожая штука и с Boost.Spirit. Короче, невзлюбил я с первого взгляда Variant, Xpressive и Spirit. Впрочем, оставляю за собой право передумать :)

* Помню, у вас, геймдевщиков, как-то холивар состоялся (на dtf, емнип). На тему «саттеры, мэйерсы, александреску и студенты любители c++-дрочерства умных книжек». А теперь представь, выдаю я на работе нагора исходник, а там сплошь boost::make_recursive_variant_over и boost::apply_visitor. Нафиг моим коллегам такие расклады — с утра в понедельник увидеть это? :)

Ну, в общем, Boost.Variant я подробнее посмотрю. Но Boost.Any намного проще, это немаловажно.

А у вас в конторе приветствуется Boost.Variant?
[User Picture]From: [info]cyberzx
2007-10-08 01:03 am (UTC)

(Link)

Аналогия с Boost.Regexp и Boost.Xpressive немного не верна. Кстати икспрессив может и рантайм рекэкспы обрабатывать.

Между boost.variant и boost.any есть принципиальная разница.
Вариант это такой высокоуровневый юнион. Значение переменной хранится прямо в нём.
А boost.any - это что-то типа pImpl. any требует динамического выделения памяти для хранения значения переменной.

Any более гибок, так как может хранить в себе любое значение.
А Variant более дешёв. Как в создании\копировании, так и в кастах. Касты там осуществлются по средством визитора, который по сути является оператором switch и от этого он очень быстрый.

в Any же каст требует сравнения typeid, а это значит сравнение строк, операция не из дешёвых. Ну и копирование требует выделения памяти. Что тоже не очень приятно и ведёт к фрагментации оперативной памяти, особенно на маленьких типах, типа int, double, string и т.д.

Короче, резюме такое. На выбор либо быстрый и дешёвый compile-time контейнер. Либо медленный и дорогой run-time контейнер. Иногда нужно первое, а иногда второе.
[User Picture]From: [info]bik_top
2007-10-08 07:21 am (UTC)

(Link)

в Any же каст требует сравнения typeid, а это значит сравнение строк, операция не из дешёвых.
Оператор typeid возвращает ссылку на type_info. Как для него реализован оператор operator ==() зависит от реализации. Но почему ты решил, что он сравнивает строки? 0_о
[User Picture]From: [info]p_thread
2007-10-07 09:33 pm (UTC)

(Link)

нормальное введение.
на медиазоне приницпиально не пишем? =)
[User Picture]From: [info]bik_top
2007-10-07 11:30 pm (UTC)

(Link)

Ок, напишу как-нибудь и на Медиазоне ;)
[User Picture]From: [info]cyberzx
2007-10-07 10:29 pm (UTC)

(Link)

*Тут выпендрёжа ради переменная объявляется а заголовке условного оператора (как в for'е), и видна только в скоупе if'а. В реальном коде такое встречается редко*

Причём тут выпендрёж? Это очень полезная и нужная конструкция. И должна использоваться в реальном коде везде где используется динамик_каст по указателю(или аналогичный каст типа any_cast).

Ну и на счёт базового класса. По-моему, использование boost.any в качестве базового класса идиологически ничем не отличается от использования void*.
В функции void ButtonOnClick(boost::any sender, EventArgs args) в sender, как и в случае с void*, может лежать абсолютно что угодно. Интерфейса полезного boost.any не выставляет. Так что ничего полезного этот тип не даёт.

Куда полезнее будет обработчик с такой сигнатурой: void ButtonOnClick(gui::Control& sender, EventArgs args).
[User Picture]From: [info]bik_top
2007-10-07 10:52 pm (UTC)

(Link)

Это очень полезная и нужная конструкция.
Я согласен. И сожалею, что в исходниках моих коллег встречается редко. Вот и напомнил, что так тоже можно (нужно). Эта конструкция полезна ещё и из общих соображений локальности имён.
[User Picture]From: [info]bik_top
2007-10-07 11:26 pm (UTC)

(Link)

Куда полезнее будет обработчик с такой сигнатурой:
void ButtonOnClick(gui::Control& sender, EventArgs args).


Тут я согласен :-` неудачный пример. Возможно, логичнее было бы boost::any взять вместо EventArgs, в качестве корня иерархии, где базовый класс пуст (так как не содержит общих для всех функций элементов).
У меня такое было как-то: была простая фабрика, там регистрировались функции-creator'ы. Вот аргументы, требующиеся для создания объектов, были разные, там Boost.Any был вполне уместен. Как альернатива пустому базовому классу BaseCreationArgs.

По-моему, использование boost.any в качестве базового класса идиологически ничем не отличается от использования void*. В функции void ButtonOnClick(boost::any sender, EventArgs args) в sender, как и в случае с void*, может лежать абсолютно что угодно. Интерфейса полезного boost.any не выставляет.

0_o Если за void* будет лежать что-то левое, то проверить корректность даун_каста нельзя будет, так ведь? В предложенном же варианте any_cast вернёт NULL или исключение, если в аргументе что-то левое.
[User Picture]From: [info]idelite
2007-10-08 08:16 pm (UTC)

(Link)

>// Жаль, что в C# нельзя сделать что-нибудь вроде
>// namespace swf = System.Windows.Forms;


Вобщем-то можно...
using swf = System.Windows.Forms;
[User Picture]From: [info]bik_top
2007-10-08 09:34 pm (UTC)

(Link)

От холера, а мужики-то не знают! Книшку, что ли, по C# почитать...
From: (Anonymous)
2007-12-29 12:05 am (UTC)

тип потока и невставляемые объекты

(Link)

Привет!

1) А нельзя ли как-нибудь сделать так, чтобы можно было Utils::Any вставлять в любой поток basic_ostream<?> (например, std::wostream)?

2) Не такой уж Utils::Any, т.к. он не работает с типами, для которых не определён оператор вставки в поток. Не могу найти откуда взялся is_default_streamable, по-этому приведу его код:

// (C) Copyright David Abrahams 2004.
// (C) Copyright Jonathan Turkanis 2004.
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.

#ifndef BOOST_IOSTREAMS_DEFAULT_INSERTABLE_HPP_INCLUDED
#define BOOST_IOSTREAMS_DEFAULT_INSERTABLE_HPP_INCLUDED

#include <iosfwd>
#include <boost/type_traits/remove_cv.hpp>
#include <boost/type_traits/add_reference.hpp>
#include <boost/mpl/bool.hpp>
#include <boost/detail/workaround.hpp>

namespace boost { namespace io {

// This namespace ensures that ADL doesn't mess things up.
namespace default_insertable_ {

// a type returned from operator++ when no increment is found in the
// type's own namespace
struct tag { };

// any soaks up implicit conversions and makes the following
// operator<< less-preferred than any other such operator that
// might be found via ADL.
struct any { template <class T> any(T const&); };

// This is a last-resort operator<< for when none other is found
template<typename Ch, typename Tr>
tag operator<<(std::basic_ostream<Ch, Tr>&, const any&);

// two check overloads help us identify which operator<< was picked
char (& check(tag) )[2];

template<typename Ch, typename Tr>
char check(std::basic_ostream<Ch, Tr>&);

template<typename T, typename Ch, typename Tr>
struct impl {
static typename add_reference< typename remove_cv<T>::type >::type t;
static std::basic_ostream<Ch, Tr>& out;

BOOST_STATIC_CONSTANT(
bool, value = sizeof(default_insertable_::check(out << t)) == 1
);
};

} // End namespace default_insertable_.

template< class T,
typename Ch = char,
typename Tr = std::char_traits<Ch> >
struct is_default_insertable
: mpl::bool_<
::boost::io::default_insertable_::impl<T, Ch, Tr>::value
>
{ };

} } // End namespace boost::io.

#endif // BOOST_IOSTREAMS_DEFAULT_INSERTABLE_HPP_INCLUDED

Спасибо за статью.

-MyXa-
[User Picture]From: [info]bik_top
2007-12-29 02:07 pm (UTC)

Re: тип потока и невставляемые объекты

(Link)

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

Advertisement