有什么理由去寻找继承/多态的替代方案吗?

时间:2014-11-24 19:55:18

标签: c++ inheritance collections polymorphism iteration

我经常需要能够迭代具有相似但不相同功能的对象集合(想象一下Task个对象的集合,这些对象都有自己的Do()实现例如,功能。)

我通常通过拥有所有任务派生的基础Task类(具有虚拟Do())来实现此目的。然后,我可以在vector<Task*>vector<unique_ptr<Task>>中收集这些内容。

是否有任何理由(或者实际上,可行的方式)以不同的方式做到这一点?

修改

为了简单起见,我使用(虚构的)Task个对象纯粹作为一个例子。实际上,实际的当前项目的典型案例是UI组合框架。在每个布局更新过程中,一个&#34;可视树&#34;从根容器遍历并且其子控件以递归方式排列(子容器控件具有其他控件作为子项等),基于子属性(如偏移,对齐,大小等)。父母根据其类型和配置对孩子进行不同的定位(想想WPF的Canvas,Grid,StackPanel等。

树在运行时不断变化,通过在容器和其他动态/用户激发的行为之间拖放视觉元素,控件本身是一个不断扩展的系列(通过插件支持新的控件类型)

3 个答案:

答案 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;你提出的方案。例如,当您需要算法中的多态行为(混合,策略,访问者等)时,有更多理由可以使用替代方案。