2014-11-24 3 views
0

Мне часто приходится иметь возможность выполнять итерацию по совокупности объектов, которые имеют сходную, но не идентичную функциональность (представьте себе коллекцию объектов Task, которые имеют собственную реализацию функции Do(), например).Любые причины для поиска альтернатив наследованию/полиморфизму?

Обычно я реализую это, имея базовый класс Task (с виртуальным Do()), из которого вытекают все задачи. Затем я могу собрать их в vector<Task*> или vector<unique_ptr<Task>>.

Есть ли причины (или, действительно, жизнеспособные способы) сделать это по-другому?

Edit:

Я использовал (мнимая) Task объекты исключительно в качестве примера для простоты. В действительности, типичный случай из фактического текущего проекта будет структурой состава пользовательского интерфейса. При каждом прохождении обновления макета «визуальное дерево» перемещается из корневого контейнера, а его дочерние элементы управления рекурсивно (дочерние элементы управления контейнерами имеют другие элементы управления как дочерние элементы и т. Д.) На основе дочерних свойств, таких как смещение, выравнивание, размер и т. Д. Родители позиционируйте своих детей по-разному в соответствии с их типом и конфигурацией (подумайте о холсте WPF, Grid, StackPanel и т. д.).

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

+1

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

+0

unique_ptr на самом деле нет RAII. Реализация RAII была бы задачей обработки уникальности. –

+0

@ DieterLücking 'unique_ptr ' является не чем иным, как указателем на объект Task-_derived_, но где (для всех целей и целей) объект «хранится в векторе» (т. Е. Вам не нужно хранить его в другом месте (постоянно), чтобы иметь возможность поместить в него указатель в вектор, как и с вектором '). 'vector ' не соответствует заданию, очевидно. – d7samurai

ответ

1

Ну, если вы хотите быть максимально стандартизован для всего «коллекции задач», вы можете использовать std::function:

std::vector< 
    std::function<void()> 
> tasks; 

Таким образом, ваши задачи не все должны наследовать от Task. Или даже быть объектами.

void printHello() { cout << "Hello\n"; } 

tasks.push_back(printHello); 
tasks.push_back([]{ /* do stuff */ }); 

struct Object // doesn't inherit from anything 
{ 
    void operator()() const { 
     // do other stuff 
    } 
}; 

tasks.push_back(Object{}); 

Этот метод называется стиранием типа.

+0

Мой пример объектов 'Task' с простой функцией' Do() 'был значительно упрощен. Я обновил вопрос, приблизившись к реальному сценарию. – d7samurai

+0

@ d7samurai Если вы работаете с коллекцией объектов пользовательского интерфейса, которые вам нужно делать с последующим да, это похоже на довольно стандартный вариант использования для полиморфизма. Что вам не нравится? – Barry

+0

У меня нет ничего против этого - только в последнее время я слышал, что программисты, которых я уважаю, отношусь к полиморфизму как к глупой концепции (в пользу «гораздо более мощных» способов делать что-то вроде mixins и т. Д.), И мне было интересно, почему это может быть (и если бы были альтернативы наследованию и т. д. для моих случаев использования). – d7samurai

1

«Есть ли причины по-другому?»

Да, есть:
Подумайте дважды, если вам действительно нужно во время выполнения полиморфизм, это может быть ненужным падение производительности для конкретных ситуаций.

Возможно, вам удастся использовать статический полиморфизм, если все ваши реализации Task известны во время компиляции. См. CRT pattern, как реализовать статический полиморфизм.


«Не могли бы вы подробнее остановиться на этом?"

Ну, я постараюсь (как вы на самом деле просят для интерфейса virtual необходимого из-за отсутствия времени компиляции известного Plugin интерфейс):

Вы можете иметь (чистый) virtual базовый интерфейс реализован некоторыми CRTP базового класса:

struct TaskInterface { 
    virtual void Do() = 0; 
    virtual ~TaskInterface() {} 
}; 

Реализация может быть обеспечена CRTP:

template<class Impl> 
class TaskBase 
: public TaskInterface { 
    virtual void Do() { 
     DoDerivedImpl(); 
    } 
protected: 
    void DoDerivedImpl() { 
     static_cast<Impl*>(this)->DoImpl(); 
    } 

    void DoImpl() { 
     // Issue a static_assert error here, that there's no appropriate overridden 
     // implementation of DoImpl() available: 
     static_assert 
      (static_cast<Impl*> (this)->DoImpl != TaskBase<Impl>::DoImpl 
      , "TaskBase requires an appropriate implementation of DoImpl()"); 
    } 
}; 

class TaskType1 : public TaskBase<TaskType1> { 
public: 
    void DoImpl() { 
     cout << "TaskType1::DoImpl()" << endl; 
    } 
}; 

class TaskType2 : public TaskBase<TaskType2> { 
public: 
    void DoImpl() { 
     cout << "TaskType2::DoImpl()" << endl; 
    } 
}; 

class TaskType3 : public TaskBase<TaskType3> { 
    // Missing DoImpl() 
}; 

int main() { 
    std::vector<TaskInterface*> tasks; 
    TaskType1 t1; 
    TaskType2 t2; 
    // TaskType3 t3; // Uncomment to see compile time errors 
    tasks.push_back(&t1); 
    tasks.push_back(&t2); 
    // tasks.push_back(&t2); 

    for(std::vector<TaskInterface*>::iterator it = tasks.begin(); 
     it != tasks.end(); 
     ++it) { 
      (*it)->Do(); 
    } 
} 

См LIVE DEMO для регулярного составления реализации.
См. LIVE DEMO за неудобство использования TaskType3.


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

+0

@Barry Это было намеренно: 'P' для _'pattern'_ избыточно! –

+1

Да, но термин «CRTP». Может быть излишне говорить о CRTP-шаблоне (например, в банкомате), но CRT для меня - это электронно-лучевая трубка. Возможно, просто «Смотрите CRTP, как реализовать ...»? – Barry

+0

@Barry Ссылка все равно уточняется ... –

0

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

Но есть определенные причины для использования альтернативы или ее вариации. Большинство из этих причин - «особые случаи».

Одним из распространенных вариантов является использование некоторой формы обертывания динамического полиморфизма в семантические классы значений. Один вариант в этом семействе использует стирание типа (например, std::function). Есть причины, по которым это лучше, потому что в некоторых ситуациях оно может быть более легким (например, функтор без учета состояния для функции «Do()»). Другой причиной этого является также то, что вы не захотите привязать свои классы к базовому классу «Задача», или у вас могут быть существующие классы, которые вы хотите адаптировать для этой цели неинтрузивным способом (не изменяя их наследование) ,

Другой вариант - не использовать виртуальные функции и наследование вообще. Например, если ваш набор «производных классов» мал и содержит очень похожие классы (те же элементы данных), то вы можете получить некоторые преимущества в производительности, чтобы иметь возможность хранить эти объекты по значению внутри вектора (т. Е. Шаблоны доступа к памяти будут быть более прямым и эффективно упакованным в кэш-память). Если в реализации функции «Do()» есть только небольшие вариации, возможно, стоит просто реализовать эти разные поведения внутри одного класса (например, с помощью оператора switch внутри функции Do), которые могут быть сохранены по значению.

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

Но в целом я бы сказал, что альтернативы динамическому полиморфизму более подходят в других случаях, а не сценарий «сбор различных объектов», который вы только что внесли. Например, когда вам нужно полиморфное поведение в алгоритмах (миксины, политики, посетители и т. Д.), то есть гораздо больше причин прибегать к альтернативе.

+0

Слишком долго, и я даже прочитал его. (В чем смысл?) –

+0

@ DieterLücking Суть в том, чтобы дать серьезный и несколько всеобъемлющий ответ на вопрос ... Я думаю, это не то, что обычно встречается здесь на SO. Думаю, мне следовало бы сделать так, как это делают резидентные кодовые дебаты SO: выкрикивайте несколько слов, которые они знают (например, RAII, стирание стилей и CRTP), повторяют одни и те же старые точки разговора, а затем публикуют первые несколько ссылок, которые появляются в поиске Google. Вы предпочли бы это? Или был этот комментарий также TL; DR? –

Смежные вопросы