Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Идиомы С . Type erasure

Anna | 24.06.2014 | нет комментариев
Хотите получить представление о том, как устроен boost::functionboost::any “под капотом”? Узнать либо освежить в памяти, что скрывается за малопонятной фразой “стирание типа”? В этой статье я постараюсь коротко высказать мотивацию, стоящую за этой идиомой и ключевые элементы реализации.

Мотивация

Как положить в один контейнер объекты никак не связанных друг с ином типов? Скажем, прочитанные из командной строки опции сразу “разложить” по различным типам и положить в цельный контейнер. Либо беречь внутри одного объекта “нечто” произвольного типа с исключительным лимитацией — наличием оператора “()” у хранимого “нечто”? Как, в всеобщем случае, “стереть” тип всякого объекта, спрятав его за объектом иного, некоторого всеобщего типа?

void*

На самом деле в С есть встроенный механизм, дозволяющий спрятать тип всякого объекта за всеобщим типом. Это — доставшийся в наследство от С, указатель void*.

Его дозволено применять, скажем, так:

struct A{  void foo(); };
struct B{  int bar(double); };
A a;
B b;
std::vector<void*> v;
v.push_back(&a);
v.push_back(&b);

static_cast<A*>(v[0])->foo();
static_cast<B*>(v[1])->bar(3.5);

Либо так:

class void_any
{
public:
	void_any(const void* h, size_t size) : size_(size)
	{
		h_ = std::malloc(size);
		std::memcpy(h_, h, size);
	}
	void get(void*& h)
	{
		h = std::malloc(size_);
		std::memcpy(h, h_, size_);
	}
	~void_any(){ std::free(h_); }
private:
	size_t size_;
	void* h_;
};

int some_int=675321;
void_any va(&some_int, sizeof(int));
void* pi;
va.get(pi);
std::cout << *(int*)pi << std::endl;

Такая схема будет трудиться, но, думаю, её недочеты очевидны. Дозволено ошибиться при касте, передать неверный размер в конструктор, невозможно применять с rvalue выражениями. Мы принуждаем пользователя помнить о том, объект какого именно типа хранится в указателе и “вручную” приводить к этому типу. Ну а самое основной недочет, вероятно, в том, что мы никак не используем систему типов языка на котором пишем. Все равно что забивать гвоздь шуруповертом. Дозволено, но неудобно. Так как же быть?

Образцы и наследование

Вы теснее вероятно додумались, что без образцов тут не обойдется. Да, подлинно, в конструктор шаблонного класса (шаблонную функцию) дозволено передать объект всякого типа и, тем самым, спрятать его тип, но этим мы не решим 2-й задачи, а именно, спрятать объект всякого типа за объектом одного всеобщего типа.

template <typename T>
struct some_t{};
some_t<int> s1;
some_t<double> s2;

Во фрагменте выше s1 и s2 позже инстанциирования являются объектами безусловно различных, несвязанных типов.
К счастью, С не ограничивается одними образцами. И нам на поддержка придет наследование и динамический полиморфизм. Читайте дальнейший раздел, Дабы осознать как именно.

Реализация

Выходит, от слов к делу. Нам теснее ясно, что наша “обертка” не должна быть образцом, но при этом должна быть способна в конструкторе принять объект всякого типа. Как это допустимо? Верно, с поддержкой шаблонного конструктора.

class any
{
public:
	template<typename T>
	any(const T& t);
//…
};

Но как сейчас сберечь то, что нам передали в конструкторе? Наш класс ничего не знает о типе Т, параметризующем конструктор, следственно так написать мы не можем:

class any
{
//...
private:
	T t_;
};

Для решения этой задачи мы будем беречь указатель на абстрактную вспомогательную конструкцию, а переданное нам в конструкторе t, отдадим в конструкцию-образец, наследующую от абстрактной вспомогательной базы.

class any
{
public:

any(const T& t) : held_(new holder<T>(t)){}
//…
private:
	struct base_holder
	{
		virtual ~base_holder(){}
	};

	template<typename T> struct holder : base_holder
	{
		holder(const T& t) : t_(t){}
		T t_;
	};
private:
	base_holder* held_;
};

Отменно! Сейчас мы можем сберечь объект всякого типа в классе “any”. Дело за малым, сейчас сохраненный объект нужно при необходимости каким-то образом “достать” из недр нашей обертки. Для этого, к сожалению, нам придется воспользоваться RTTI. Добавим функцию, возвращающую информацию о типе хранимого значения в наши вспомогательные конструкции.

struct base_holder
{	//...
	virtual const std::type_info& type_info() const = 0;
};

template<typename T> struct holder : base_holder
{	//...
	const std::type_info& type_info() const
	{
		return typeid(t_);
	}
};

Сейчас написать функцию возвращения начального объекта не составит большого труда.

template<typename U>
U cast() const
{
	if(typeid(U) != held_->type_info())
		throw std::runtime_error("Bad any cast");
	return static_cast<holder<U>* >(held_)->t_;
}

Отчего RTTI необходимо применять к сожалению? Потому что, хотелось бы написать что-то как бы такого, Дабы перенести проверку типа в compile time:

U cast(typename std::enable_if<std::is_same<U, decltype(
        static_cast<holder<U>* >(held_)->t_)>::value>::type* = 0) const
	{
		return static_cast<holder<U>* >(held_)->t_;
	}

Отчего такое решение не подходит? Дело в том, что

std::is_same<U, decltype(static_cast<holder<U>* >(held_)->t_)>::value

неизменно будет true, самостоятельно от того какой на самом деле тип объекта, хранящегося в holder. Такой код будет компилироваться и даже выполняться без падений (если повезет)

any a(2);
a.cast<std::string>();

Но итоги будут вовсе не те, что ждет программист.

В классе boost::function применяется тот же правило стирания типа. Косметические различия заключаются в том, что function — образец, параметризуемый типами возвращаемого значения и доводов, а во вспомогательных конструкциях возникает функция

virtual return_type operator()(arg_type1, .., arg_typeN);

Листинг

class any
{
public:
	template<typename T>
	any(const T& t) : held_(new holder<T>(t)){}
	~any(){ delete held_; }
	template<typename U>
	U cast() const
	{
		if(typeid(U) != held_->type_info())
			throw std::runtime_error("Bad any cast");
		return static_cast<holder<U>* >(held_)->t_;
	}
private:
	struct base_holder
	{
		virtual ~base_holder(){}
		virtual const std::type_info& type_info() const = 0;
	};

	template<typename T> struct holder : base_holder
	{
		holder(const T& t) : t_(t){}
		const std::type_info& type_info() const
		{
			return typeid(t_);
		}
		T t_;
	};
private:
	base_holder* held_;
};

int main() 
{
	any a(2);
	std::cout << a.cast<int>() << std::endl;
	any b(std::string("abcd"));
	try
	{
		std::cout << b.cast<double>() << std::endl;
	}
	catch(const std::exception& e)
	{
		std::cout << e.what() << std::endl;
	}
	return 0;
}

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB