我经常需要能够迭代具有相似但不相同功能的对象集合(想象一下Task
个对象的集合,这些对象都有自己的Do()
实现例如,功能。)
我通常通过拥有所有任务派生的基础Task
类(具有虚拟Do()
)来实现此目的。然后,我可以在vector<Task*>
或vector<unique_ptr<Task>>
中收集这些内容。
是否有任何理由(或者实际上,可行的方式)以不同的方式做到这一点?
修改
为了简单起见,我使用(虚构的)Task
个对象纯粹作为一个例子。实际上,实际的当前项目的典型案例是UI组合框架。在每个布局更新过程中,一个&#34;可视树&#34;从根容器遍历并且其子控件以递归方式排列(子容器控件具有其他控件作为子项等),基于子属性(如偏移,对齐,大小等)。父母根据其类型和配置对孩子进行不同的定位(想想WPF的Canvas,Grid,StackPanel等。
树在运行时不断变化,通过在容器和其他动态/用户激发的行为之间拖放视觉元素,控件本身是一个不断扩展的系列(通过插件支持新的控件类型)
答案 0 :(得分:1)
如果您希望尽可能通用&#34;任务集合&#34;,您可以使用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{});
这种技术被称为&#34;类型擦除&#34;。
答案 1 :(得分:1)
&#34;有不同的理由吗?&#34;
是的,有:
三思而后行,如果你真的需要运行时多态性,那么在特定情况下它可能是不必要的性能损失。
如果在编译时已知所有Task
实现,那么使用静态多态可能会很顺利。请参阅CRT pattern,了解如何实现静态多态。
&#34;你能详细说明吗?&#34;
好吧,我会尝试(由于你已经知道插件接口的非编译时间,你实际上需要一个virtual
接口:
您可以使用某些CRTP基类实现的(纯)virtual
基接口:
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
有关TaskType3
的未注释使用情况,请参阅LIVE DEMO。
优点是,您可以轻松地为多个界面使用多个mixin实现,以设置最终的&#39; 类。
答案 2 :(得分:0)
您已经描述了一些基本上是动态多态性的规范用例。它经常被用作OOP中的一个例子,因为很明显动态多态在这种一般情况下非常(大多数)适合。
但使用替代品或其变体肯定有一些原因。其中大部分原因都是&#34;特殊情况&#34;。
一种常见的变体是使用某种形式的动态多态性包装到值语义类中。该族中的一个变体是使用类型擦除(例如,std::function
)。有理由说这更好,因为在某些情况下它可以更轻量级(例如,&#34; Do()&#34;函数的无状态仿函数)。另一个原因还在于您可能不希望将您的课程绑定到&#34;任务&#34;基类,或者您可能希望以非侵入方式(不更改其继承)来适应此目的的现有类。
另一种选择是根本不使用虚函数和继承。例如,如果您的&#34;派生类&#34;虽然很小并且包含非常相似的类(相同的数据成员),但是您可以获得一些性能优势,以便能够通过值在向量内存储这些对象(即,内存访问模式将更直接且有效地打包在高速缓存上)。如果&#34; Do()&#34;的执行只有很小的变化。函数,在一个单独的类中简单地实现那些不同的行为(例如,在Do函数中使用switch语句)可能是值得的,可以按值存储。
当然,如果你不需要运行时机制,那么你就不应该使用动态多态,而是使用静态多态。
但总的来说,我会说动态多态的替代方案在其他情况下更合适,而不是&#34;不同对象的集合&#34;你提出的方案。例如,当您需要算法中的多态行为(混合,策略,访问者等)时,有更多理由可以使用替代方案。