Я хочу сделать интерфейсные классы ForwardIterator
, BiderctionalIterator
и RandomAccessIterator
, каждый из которых имеет несколько операторов (все они чисто виртуальные и не реализованы). Теперь, если я хочу реализовать итератор для контейнера, я просто наследую правильный итератор и получаю помощь компилятора, если я случайно забыл реализовать некоторые функции/операторы.
Выполнение этого с чистыми виртуальными функциями работает отлично, но у него есть накладные расходы vtable, которые не нужны, поскольку все потребности кода могут быть определены во время компиляции.
template <typename T>
struct ForwardIterator{
virtual T operator++() = 0;
virtual T operator++(int) = 0;
};
template <typename T>
struct BidirectionalIterator: public ForwardIterator<T>{
virtual T operator--() = 0;
virtual T operator--(int) = 0;
};
template <typename T>
struct RandomAccessIterator: public Bidirectional<T>{
virtual T operator+(int) = 0;
virtual T operator-(int) = 0;
};
// Custom Iterator Implementation
class MyCustomRandomAccessIterator
: public RandomAccessIterator<MyCustomRandomAccessIterator>{
// get errors if I miss some function definitions.
// but it has vtable !!!
};
С++ 20 имеет concept
.
Что вам нужно, так это любопытно повторяющийся шаблон шаблона https://en.m.wikipedia.org/wiki/Curiiously_recurring_template_pattern Это допускает статический полиморфизм, то есть вы можете привести указатель this к указателю дочернего класса, для которого вы передаете тип в качестве параметра шаблона и вызываете методы из дочернего класса в базовом классе без vtable. Чтобы различать тип итератора, вы также передаете тег итератора, соответствующий типу, в качестве аргумента шаблона базовому классу. Например, так работает Boost.STLInterfaces (https://www.boost.org/doc/libs /1_74_0/doc/html/stl_interfaces.html).
Похоже, это должен был быть комментарий, если вы не объясните, как это поможет.
Добавлено небольшое пояснение, хотя вики-страница уже объясняет это очень хорошо.
И как это поможет проверить тип при простом создании объекта MyCustomRandomAccessIterator
?
Если я правильно понимаю, вы хотите быть уверены, что некоторые методы реализованы в окончательных производных классах, но без использования классического способа (абстрактного наследования), потому что это слишком тяжело.
Моей первой идеей было просто объявить, а не определять методы
template <typename T>
struct ForwardIterator {
T operator++ ();
T operator++ (int);
};
поэтому, когда вы используете методы
MyCustomRandomAccessIterator i;
++i;
вы получаете ошибку компоновщика.
Но вы получаете ошибку компоновщика (и я полагаю, вы предпочитаете ошибку компиляции) и только тогда, когда вы используете забытый метод, поэтому, если вы забудете один метод, его будет сложно обнаружить (вам нужно использовать его с объектом).
Итак, моя идея, возможно, не идеальна, это delete
методы, добавить конструктор и проверить наличие методов в финальном классе.
Я имею в виду... что-то вроде
template <typename T>
struct ForwardIterator {
T operator++ () = delete;
T operator++ (int) = delete;
ForwardIterator ()
{
static_assert( sizeof(decltype(std::declval<T>()++)), "!" );
static_assert( sizeof(decltype(++std::declval<T>())), "!" );
}
};
Таким образом, вы получаете все ошибки компиляции (может быть, если хотите, с осмысленными сообщениями об ошибках, лучше, чем "!"
), просто объявляя объект вашего класса.
MyCustomRandomAccessIterator i;
// ++i; // no needs of invoke the forgotten methods to get the errors
Ниже приведен полный пример компиляции
#include <utility>
template <typename T>
struct ForwardIterator {
T operator++ () = delete;
T operator++ (int) = delete;
ForwardIterator ()
{
static_assert( sizeof(decltype(std::declval<T>()++)), "!" );
static_assert( sizeof(decltype(++std::declval<T>())), "!" );
}
};
template <typename T>
struct BidirectionalIterator: public ForwardIterator<T> {
T operator-- () = delete;
T operator-- (int) = delete;
BidirectionalIterator ()
{
static_assert( sizeof(decltype(std::declval<T>()--)), "!" );
static_assert( sizeof(decltype(--std::declval<T>())), "!" );
}
};
template <typename T>
struct RandomAccessIterator: public BidirectionalIterator<T>{
T operator+ (int) = delete;
T operator- (int) = delete;
RandomAccessIterator ()
{
static_assert( sizeof(decltype(std::declval<T>()+0)), "!" );
static_assert( sizeof(decltype(std::declval<T>()-0)), "!" );
}
};
// Custom Iterator Implementation
struct MyCustomRandomAccessIterator
: public RandomAccessIterator<MyCustomRandomAccessIterator> {
// with `if 0` (forgetting definition of requested methods) you get
// a lot of compilation errors
#if 1
MyCustomRandomAccessIterator operator++ ()
{ return *this; }
MyCustomRandomAccessIterator operator++ (int)
{ return *this; }
MyCustomRandomAccessIterator operator-- ()
{ return *this; }
MyCustomRandomAccessIterator operator-- (int)
{ return *this; }
MyCustomRandomAccessIterator operator+ (int)
{ return *this; }
MyCustomRandomAccessIterator operator- (int)
{ return *this; }
#endif
};
int main()
{
// simply declaring i you get a lot of errors, in case of no
// methods definitions
MyCustomRandomAccessIterator i;
}
Это очень умное решение. Я предполагаю, что нет накладных расходов на память или производительность, верно?
@ 0xDEADC0DE - Накладные расходы на память или производительность? Нет, я полагаю, что нет (но я не эксперт в практической реализации компилятора, поэтому проверьте себя с помощью своего компилятора).
Вы можете посмотреть на имеет ли смысл статический полиморфизм для реализации интерфейса.