Pull to refresh

Comments 7

Если честно, не понял по поводу slice:
Компилятор сделает срез базового типа по отнаследованному. И если указатель увеличился с 1, значит объект был срезан.

Можете поподробней рассказать вот это место:
И если указатель увеличился с 1, значит объект был срезан


У вас же Message — полиморфный и вы передаёте его по ссылке, можно восстановить объект. Как-то так:
struct A
{
	int a_;

	A(int a)
		: a_(a)
	{
	}

	virtual ~A()
	{
	}
};

struct B :
	public A
{
	int b_;

	B(int a, int b)
		: A(a)
		, b_(b)
	{
	}
};

struct C :
	public A
{
	int c_;

	C(int a, int c)
		: A(a)
		, c_(c)
	{
	}
};

void test(const A& a)
{
	try
	{
		const B& b = dynamic_cast<const B&>(a);
		std::cout << b.a_ << " " << b.b_ << std::endl;
	}
	catch(const std::bad_cast& e)
	{
		std::cout << "test: @a is not B: " << e.what() << std::endl;
	}
}

int main()
{
	test(C(10, 20));
	test(B(10, 30));
	return 0;
}


Вывод:
test: @a is not B: Bad dynamic_cast!
10 30
Да, спасибо. Я не совсем верно употребил по смыслу slice. Я отбрасываю из решения частные случаи slice. Там где указатель на класс наследника и указатель на базовый класс начинают отличаться. В вашем примере с этим всё хорошо (https://ideone.com/9vzMCE).

По смыслу восстановить объект с dynamic_cast можно. И это вариант более полного решения, нежели приведённое. Я скорее хотел обойтись без dynamic_cast, используя фактически reinterpret_cast для ссылок. И поэтому в таком решении приходится отсеивать такие случаи.
Спасибо. Вы имеете в виду случай, когда начинается полный ад? — что-то такое:)
Да, именно этот случай. Полный ад случится, если при незначительном изменении структуры сообщения, компилятор по внутренним соображениям выравнивания переставит наследуемые классы (емнип, порядок расположения в стандарте не определён). И внезапно где-то в коде всё сломается!
Со срезкой, как мне кажется, проблема надумана. Мы же копированием объектов не занимаемся?
Поэтому вполне можем себе позволить смещение базы туда и обратно:
class Base { ..... };
class Derived : ....., Base, ..... { ..... };

Derived d;
Derived* pd = &d;
Base* pb = /* static_cast<Base*> */ pd;

assert( (void*)pb != (void*)pd ); // пусть у нас база смещена, иначе как-то банально будет...

Derived* pd2 = static_cast<Derived*>(pb);
assert( pd2 == pd ); // это условие должно выполниться в любом случае


Но для этого надо избавиться от реинтерпретов.
А чтобы избавиться от реинтерпретов, в свою очередь, можно воспользоваться идиомой невиртуального интерфейса: вместе с указателями на объект и его метод сохранять указатель на вспомогательную функцию, которая умеет правильно вызывать метод объекта.

Сейчас попробую сделать эскиз…
ideone.com/QZrLxf — пример делегата, устойчивого к сдвигу базы
#include <cstdio>

// обобщённый указатель на метод
struct GenericObject; // класс должен быть неизвестен заранее, тогда компилятор сделает наиболее общий тип метода
typedef void (GenericObject::*GenericMethod)(GenericObject const&);

template<class HandlerBase, class ArgBase>
struct CallTraits
{
	typedef void(*CallFunc)(HandlerBase*, GenericMethod, ArgBase const&);

	template<class TheHandler, class TheArg>
	static void callfunc(HandlerBase* h, GenericMethod m, ArgBase const& a)
	{
		typedef void (TheHandler::*TheMethod)(TheArg const&);

		TheHandler*   th = (TheHandler*)h;
		TheMethod     tm = (TheMethod)m;
		TheArg const& ta = (TheArg const&)a;

		printf(" < %p->...(*%p)\n", h,  &a);
		printf(" > %p->...(*%p)\n", th, &ta);
		(th->*tm)(ta);
	}

	struct Delegate
	{
		HandlerBase*  h;
		GenericMethod m;
		CallFunc      c;
	};

	template<class TheHandler, class TheArg>
	static Delegate make_delegate(TheHandler* th, void (TheHandler::*tm)(TheArg const&))
	{
		Delegate d = { (HandlerBase*)th, (GenericMethod)tm, &callfunc<TheHandler,TheArg> };
		return d;
	}

	static void call_delegate(Delegate const& d, ArgBase const& a)
	{
		d.c(d.h, d.m, a);
	}
};

struct A0 { int x; virtual ~A0() {} };
struct A1 { int y; };
struct A : A0, A1 {}; // хорошенько сдвинем базу...


struct H0 { int x; virtual ~H0() {} };
struct H1 { int y; virtual ~H1() {} }; // и усложним себе жизнь, добавив виртуальные функции
struct H : H0, H1
{
	virtual void foo(A const& a)
        // чтобы жизнь мёдом не казалась! виртуальная функция с большей вероятностью грохнется, если мы напутаем
	{
		printf(" = %p->foo(*%p)\n", this, &a);
	}
};





int main()
{
	typedef CallTraits<H1, A1> CT;

	A a; H h;

	printf("directly:\n");
	h.foo(a);

	printf("delegated:\n");
	CT::Delegate d = CT::make_delegate(&h, &H::foo);
	printf("delegate: %p %p %d %d\n", d.h, d.c, sizeof(d.c), sizeof(d.m));
	CT::call_delegate(d, a);
}


Получаем:
directly:
 = 0xbf9fb780->foo(*0xbf9fb774)
delegated:
delegate: 0xbf9fb788 0x80487a0  sizeof(fun)=4 sizeof(method)=8
 < 0xbf9fb788->...(*0xbf9fb77c)    -- видно, что и обработчик, и аргумент сдвинуты...
 > 0xbf9fb780->...(*0xbf9fb774)    -- мы их корректно восстанавливаем...
 = 0xbf9fb780->foo(*0xbf9fb774)    -- и вызываем правильную функцию!
Я действительно не подумал решить проблему, а решил её просто вырезать. Вышло здорово, спасибо. Хоть HabraCallbacks пиши с лямбдами и поэтессами.
Sign up to leave a comment.

Articles