Реализация абстрактного класса в качестве интерфейса для других классов без накладных расходов vtable

Я хочу сделать интерфейсные классы 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.

Jarod42 25.12.2020 15:10
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
2
211
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Что вам нужно, так это любопытно повторяющийся шаблон шаблона 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).

Похоже, это должен был быть комментарий, если вы не объясните, как это поможет.

Ted Lyngmo 25.12.2020 12:45

Добавлено небольшое пояснение, хотя вики-страница уже объясняет это очень хорошо.

Yamahari 25.12.2020 12:50

И как это поможет проверить тип при простом создании объекта MyCustomRandomAccessIterator?

StoryTeller - Unslander Monica 25.12.2020 12:52
Ответ принят как подходящий

Если я правильно понимаю, вы хотите быть уверены, что некоторые методы реализованы в окончательных производных классах, но без использования классического способа (абстрактного наследования), потому что это слишком тяжело.

Моей первой идеей было просто объявить, а не определять методы

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 25.12.2020 14:32

@ 0xDEADC0DE - Накладные расходы на память или производительность? Нет, я полагаю, что нет (но я не эксперт в практической реализации компилятора, поэтому проверьте себя с помощью своего компилятора).

max66 25.12.2020 14:37

Другие вопросы по теме