std::vector<Derived*>
— это тип, не связанный с std::vector<Base*>
. Не существует законного способа интерпретировать память одного как другого, за исключением чего-то безумно глупого, например, размещения нового.
Ваши попытки генерируют ошибки, если вам повезет. Если нет, они генерируют неопределенное поведение, что означает, что сегодня может показаться, что это работает, но завтра они могут молча отформатировать ваш жесткий диск из-за чего-либо, от обновления компилятора до изменения кода или фазы луны.
Так вот, многие операции с vector<Base*>
работают с vector<Derived*>
. Мы можем справиться с этим с помощью стирания типа.
Вот класс стирания малоэффективного типа:
template<class R, class...Args>
using vcfunc = std::function<R(void const*, Args...)>;
template<class T, class R, class...Args, class F>
vcfunc<R,Args...> vcimpl( F&& f ) {
return [f=std::forward<F>(f)](void const* pt, Args&&...args)->R{
return f( *static_cast<T const*>(pt), std::forward<Args>(args)... );
};
}
template<class T>
struct random_access_container_view {
using self=random_access_container_view;
struct vtable_t {
vcfunc<std::size_t> size;
vcfunc<bool> empty;
vcfunc<T, std::size_t> get;
};
vtable_t vtable;
void const* ptr = 0;
template<class C,
class dC=std::decay_t<C>,
std::enable_if_t<!std::is_same<dC, self>{}, int> =0
>
random_access_container_view( C&& c ):
vtable{
vcimpl<dC, std::size_t>( [](auto& c){ return c.size(); } ),
vcimpl<dC, bool>( [](auto& c){ return c.empty(); } ),
vcimpl<dC, T, std::size_t>( [](auto& c, std::size_t i){ return c[i]; } )
},
ptr( std::addressof(c) )
{}
std::size_t size() const { return vtable.size( ptr ); }
bool empty() const { return vtable.empty( ptr ); }
T operator[](std::size_t i) const { return vtable.get( ptr, i ); }
};
Теперь это немного игрушка, поскольку она не поддерживает итерацию. (Итераторы примерно такие же сложные, как описанный выше контейнер).
Живой пример.
struct A {
char name='A';
};
struct B:A {
B(){ name='B'; }
};
void print_them( random_access_container_view<A> container ) {
for (std::size_t i = 0; i < container.size(); ++i ) {
std::cout << container[i].name << "\n";
}
}
int main() {
std::vector<B> bs(10);
print_them( bs );
}
Языки, которые позволяют обрабатывать контейнеры-потомков как списки-базы, в основном делают вышеописанные вещи автоматически. Либо сами контейнеры имеют эквивалент таблицы виртуальных функций, либо когда вы рассматриваете контейнер как представление для основы, таблица виртуальных функций синтезируется и используется клиентским кодом.
Вышеупомянутое не максимально эффективно; обратите внимание, что каждый std::function
был без гражданства. Я мог бы довольно легко заменить их указателями на функции и сохранить статическую виртуальную таблицу на основе типа C
вместо этого для экономии памяти (но добавить другую косвенность).
Мы также можем сделать это немного проще с типом без представления, поскольку мы могли бы использовать шаблон модели-концепции стирания типа вместо этого шаблона ручной vtable.
person
Yakk - Adam Nevraumont
schedule
20.09.2017