2015-03-08 3 views
4

У меня есть код, который использует несколько скрытый отбрасывания от базового класса до типа дочернего класса, где тип дочернего класса указан как параметр шаблона. Я предполагаю, что, поскольку базовый класс не объявляет никаких элементов данных и имеет нулевой размер, адрес указателя базового класса будет таким же, как и у ребенка, и приведение будет успешным. Пока код работает правильно.Вставить пустой указатель базового класса в указатель на дочерний класс?

Вот упрощенная версия того, что я делаю:

template <class RangeT> 
struct CppIterator { 
    CppIterator(const RangeT& range) { ... } 
    // ... methods calling RangeT's members 
}; 

// Base class, provides begin()/end() methods. 
template<class RangeT> 
struct CppIterableBase { 
    CppIterator<RangeT> begin() { 
     return CppIterator<RangeT>(*(RangeT*)this); // Is this safe? 
    } 
    CppIterator<RangeT> end() { ... } 
}; 

struct MyRange : CppIterableBase<MyRange> { 
    // ... 
}; 

Мой вопрос заключается в основном - это код кошерным? Будет ли указатель базы всегда приравниваться к дочернему указателю, если база пуста? Это зависит от реализации? Попаду ли я в беду?

Он решает мою проблему красиво, но я немного сомневаюсь.

+0

Почему вы (думаете) вам это нужно? –

+0

У меня есть множество типов диапазонов - наследование не позволяет мне снова и снова определять те же методы begin() и end(). – QuadrupleA

+0

Я имел в виду, почему вам нужно использовать его, а не почему вам нужен класс итератора с типом шаблона. –

ответ

2

Нет необходимости в том, чтобы базовый класс был пустым.

До тех пор, пока база доступна, недвусмысленна и не виртуальна, а указатель-на-базу фактически указывает на базовый подобъект производного объекта, он действителен для выполнения static_cast от указателя- base to pointer-to-производным. Компилятор выполнит любую настройку, необходимую для значения указателя.

Что вы делаете, на самом деле является общим шаблоном, называемым любопытно повторяющимся шаблоном шаблона (или CRTP).

reinterpret_cast - совершенно другой зверь, однако, и в этом случае, вероятно, будет билет в один конец до неопределенного поведенческого пространства (я слишком ленив, чтобы вникать в стандарт и выяснить, в каких случаях он не является " t UB). И так как литой C-стиль может стать reinterpret_cast (здесь, если вы случайно получили Foo от Base<Baz>, когда Baz не является кодом Base<Baz>), его не следует использовать.

Кроме того, есть ways, чтобы убедиться, что вы случайно не получить Foo от Base<Bar> - который просто используя static_cast не помешает, если Bar также происходит от Base<Bar>.

2

Это CRTP.

Использование static_cast<Derived*>(this) безопаснее, так как приведение C-стиля в некоторых случаях будет «слишком сильным».

Foo<> Использование для Чет базового класса CRTP:

Пока Foo<Derived> в вопросе на самом деле является базой Derived, то static_cast<Derived*>(this) безопасен во время выполнения и делает правильные вещи.

A static_assert of is_base_of может уменьшить один источник возможной ошибки здесь, но не все. Если struct Bar : Foo<Derived> вышеприведенное поведение делает неопределенное поведение, и я не знаю, как проверить эту ошибку.

+0

Вы не можете проверить в точке 'static_cast', но можно гарантировать, что создание объекта' Bar' не компилируется (для некоторого определения «обеспечить»). –