Подтвердить что ты не робот

Интерфейс С++ и шаблон

У меня есть 2 решения для одной и той же проблемы - сделать какие-то обратные вызовы от одного "контроллера" к используемому объекту, и я не знаю, что выбрать.

Решение 1: Использовать интерфейсы

struct AInterface
{
    virtual void f() = 0;
};

struct A : public AInterface
{
    void f(){std::cout<<"A::f()"<<std::endl;}
};

struct UseAInterface
{
    UseAInterface(AInterface* a) : _a(a){}
    void f(){_a->f();}

    AInterface* _a;
};

Решение 2: Используйте шаблоны

struct A
{
    void f(){std::cout<<"A::f()"<<std::endl;}
};

template<class T>
struct UseA
{
    UseA(T* a) : _a(a){}
    void f(){_a->f();}

    T* _a;
};

Это простой пример, иллюстрирующий мою проблему. В реальном мире интерфейс будет иметь несколько функций, и один класс может (и будет!) Реализовывать несколько интерфейсов.

Код не будет использоваться как библиотека для внешних проектов, и мне не нужно скрывать реализацию шаблона - я говорю это, потому что первый случай будет лучше, если мне нужно скрыть реализацию "контроллера".

Не могли бы вы рассказать мне о преимуществах/недостатках для каждого случая и о том, что лучше использовать?

4b9b3361

Ответ 1

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

Поэтому я бы сосредоточился на более высоком уровне дизайна. Имеет ли смысл, что все типы, используемые в UseA, имеют общую базу? Действительно ли они связаны? Существует ли ясная взаимосвязь между типами? Тогда подход OO может работать. Не связаны ли они? То есть, они разделяют некоторые черты, но нет прямых - отношений, которые вы можете моделировать? Перейдите к шаблону.

Основное преимущество шаблона заключается в том, что вы можете использовать типы, не соответствующие конкретной и точной иерархии наследования. Например, вы можете хранить что-либо в векторе, который является конструктивным по конструкции (с возможностью перемещения в С++ 11), но int и a Car не связаны каким-либо образом. Таким образом, вы уменьшаете связь между различными типами, используемыми с типом UseA.

Одним из недостатков шаблонов является то, что каждый экземпляр шаблона является другим типом, который не связан с остальными экземплярами шаблонов, созданными из того же базового шаблона. Это означает, что вы не можете хранить UseA<A> и UseA<B> внутри одного и того же контейнера, там будет код-bloat (UseA<int>::foo и UseA<double>::foo оба сгенерированы в двоичном формате), более длительное время компиляции (даже без учета дополнительных функций, две единицы перевода, которые используют UseA<int>::foo, будут генерировать одну и ту же функцию, и компоновщику придется отказаться от одного из них).

Что касается производительности, которую требуют другие ответы, они как-то правильны, но большинство не хватает важных моментов. Главное преимущество выбора шаблонов в динамической диспетчеризации - это не дополнительные накладные расходы на динамическую отправку, а тот факт, что небольшие функции могут быть встроены в компилятор (если само определение функции видимо).

Если функции не встроены, если только функция выполняет всего несколько циклов для выполнения, общая стоимость функции будет превзойти дополнительные затраты на динамическую отправку (т.е. дополнительное направление в вызове и возможное смещение this в случае множественного/виртуального наследования). Если функции выполняют некоторую фактическую работу и/или не могут быть встроены, они будут иметь одинаковую производительность.

Даже в тех немногих случаях, когда разница в производительности одного подхода от другого может быть измерима (скажем, что функции занимают только два цикла, а отправка таким образом удваивает стоимость каждой функции), если этот код является частью 80% кода, который занимает менее 20% времени процессора, и говорят, что этот конкретный кусок кода занимает 1% от процессора (что является огромной суммой, если вы считаете, что для производительности быть заметным сама функция должен пройти всего один или два цикла!), тогда вы говорите о 30 секундах из 1 часа запуска программы. Проверяя помещение снова, на CPU 2 ГГц, 1% времени означает, что функция должна быть вызвана более 10 миллионов раз в секунду.

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

Ответ 2

Есть плюсы и минусы каждого. На языке программирования С++:

  • Предпочитайте шаблон над производными классами, когда эффективность исполнения во время работы превзойдена.
  • Предпочтительные производные классы над шаблоном, если важно добавить новые варианты без перекомпиляции.
  • Предпочитают шаблон над производными классами, если общая база не может быть определена.
  • Предпочитают шаблон над производными классами, когда важны встроенные типы и структуры с ограничениями совместимости.

Однако шаблоны имеют свои недостатки

  • Код, который использует интерфейсы OO, может быть скрыт в файлах .cpp/.CC, когда шаблоны вынуждают выставлять весь код в файле заголовка;
  • Шаблоны вызовут раздувание кода;
  • Интерфейсы OO являются явными, когда требования к параметрам шаблона являются неявными и существуют только в голове разработчика;
  • Тяжелое использование шаблонов вредит скорости компиляции.

Что использовать зависит от вашей ситуации и от нескольких предпочтений. Шаблонный код может вызывать некоторые тупые ошибки компиляции, которые привели к таким инструментам, как расшифровка STL. Надеемся, что концепции будут реализованы в ближайшее время.

Ответ 3

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

С другой стороны, шаблон должен находиться в файле заголовка, что означает, что каждое изменение на нем приведет к перекомпиляции всех сайтов, которые его называют, в отличие от сценария интерфейса, где реализация может быть в .cpp и быть только файл, нуждающийся в перекомпиляции.

Ответ 4

Вы можете рассматривать интерфейс как контракт. Любой полученный из него класс должен реализовывать методы интерфейса.

Шаблоны, с другой стороны, неявно имеют некоторые ограничения. Например, ваш параметр шаблона T должен иметь метод f. Эти неявные требования должны быть задокументированы тщательно, сообщения об ошибках с использованием шаблонов могут быть довольно запутанными.

"Концепция Boost" может использоваться для проверки концепции, что упрощает понимание требований шаблона implcit.

Ответ 5

Выбор, который вы описываете, - это выбор между статическим полиморфизмом и динамическим полиморфизмом. Вы найдете много дискуссий по этой теме, если будете искать это.

Трудно дать конкретный ответ на такой общий вопрос. В общем, статический полиморфизм может дать вам лучшую производительность, но с отсутствием концепций в стандарте С++ 11 также означают, что вы можете получать интересные сообщения об ошибках компилятора, когда класс не моделирует требуемую концепцию.

Ответ 6

Я бы пошел с версией шаблона. Если вы думаете об этом с точки зрения производительности, тогда это имеет смысл.

Виртуальный интерфейс. Использование виртуальных средств означает, что память для метода динамична и определяется во время выполнения. У этого есть накладные расходы, поскольку он должен проконсультироваться с vlookup-таблицей, чтобы найти этот метод в памяти.

Шаблоны - вы получаете статическое сопоставление. Это означает, что когда вы вызываете свой метод, ему не нужно проконсультироваться с таблицей поиска и уже известно о расположении метода в памяти.

Если вы заинтересованы в производительности, то шаблоны почти всегда имеют выбор.