【问题标题】:Calling virtual method of base template from derived variadic template class从派生的可变参数模板类调用基模板的虚方法
【发布时间】:2014-12-10 00:46:39
【问题描述】:

这基本上是an earlier question 的后续行动(不是我提出的,但我对答案很感兴趣)。

问题是: 为什么编译器/链接器无法解析派生类对虚函数的调用?在此在这种情况下,派生类是具有可变参数的模板类,它对同一个模板类多次应用多重继承(可变参数中的每种类型一次)。

在下面的具体示例中,派生类是JobPlant,它是从Worker 调用的。调用抽象work()方法链接失败,而调用workaround()链接并按预期方式执行。

这些是链接失败,如ideone所示:

/home/g6xLmI/ccpFAanK.o: In function `main':
prog.cpp:(.text.startup+0x8e): undefined reference to `Work<JobA>::work(JobA const&)'
prog.cpp:(.text.startup+0xc9): undefined reference to `Work<JobB>::work(JobB const&)'
prog.cpp:(.text.startup+0xff): undefined reference to `Work<JobC>::work(JobC const&)'
collect2: error: ld returned 1 exit status

Follow this link 演示解决方法的工作原理。

Job 是一个抽象基类,它有相关的派生类。 Work 是一个执行工作的抽象模板类。 Worker 是一个模板,用于识别 JOB 并执行它(struct 而不是 class 纯粹是为了减少语法混乱):

struct Job { virtual ~Job() {} };

struct JobA : Job {};
struct JobB : Job {};
struct JobC : Job {};

template <typename JOB>
struct Work {
    virtual ~Work() {}
    virtual void work(const JOB &) = 0;
    void workaround(const Job &job) { work(dynamic_cast<const JOB &>(job)); }
};

template <typename PLANT, typename... JOBS> struct Worker;

template <typename PLANT, typename JOB, typename... JOBS>
struct Worker<PLANT, JOB, JOBS...> {
    bool operator()(PLANT *p, const Job &job) const {
        if (Worker<PLANT, JOB>()(p, job)) return true;
        return Worker<PLANT, JOBS...>()(p, job);
    }
};

template <typename PLANT, typename JOB>
struct Worker<PLANT, JOB> {
    bool operator()(PLANT *p, const Job &job) const {
        if (dynamic_cast<const JOB *>(&job)) {
            p->Work<JOB>::work(dynamic_cast<const JOB &>(job));
            //p->Work<JOB>::workaround(job);
            return true;
        }
        return false;
    }
};

JobPlant 是由JOBS 参数化的模板类,它找到Worker 来执行job。对于JOBS 中的每个作业类型,JobPlant 继承自 WorkMyJobPlantJobPlant 的一个实例,并实现了关联的 Work 抽象类中的虚拟 work 方法。

template <typename... JOBS>
struct JobPlant : Work<JOBS>... {
    typedef Worker<JobPlant, JOBS...> WORKER;
    bool worker(const Job &job) { return WORKER()(this, job); }
};

struct MyJobPlant : JobPlant<JobA, JobB, JobC> {
    void work(const JobA &) { std::cout << "Job A." << std::endl; }
    void work(const JobB &) { std::cout << "Job B." << std::endl; }
    void work(const JobC &) { std::cout << "Job C." << std::endl; }
};

int main() {
    JobB j;
    MyJobPlant().worker(j);
}

【问题讨论】:

    标签: c++ templates c++11 polymorphism variadic-templates


    【解决方案1】:

    你显式调用p-&gt;Work&lt;JOB&gt;::work(),即Work&lt;JOB&gt;中的纯虚方法。这个方法没有实现(毕竟是纯粹的)。

    派生类可能已经实现/覆盖了该方法并不重要。 Work&lt;JOB&gt;:: 表示您想要该类的版本,而不是派生类的版本。不会发生动态调度。

    Work&lt;JOB&gt;::work() 是用于从派生类中的覆盖方法调用基类方法的语法,而您确实需要基类方法。)


    当您删除显式 Work&lt;JOB&gt;:: 时,结果是一个歧义错误。在尝试解析名称work 时,编译器首先尝试确定哪个基类包含该名称。之后,下一步就是在该类中的不同 work 方法中进行重载解析。

    不幸的是,第一步导致歧义:多个基类包含名称work。然后编译器永远不会试图找出匹配的重载。 (它们并不是真正的重载,而是相互冲突,因为它们来自不同的类)。

    通常这可以通过使用using 将基类方法名称引入派生类来解决(或者在技术上它被称为using 所做的)。如果为基类的所有work 方法添加using 声明,编译器会在派生类中找到名称work(没有歧义),然后可以继续进行重载决策(也没有歧义)。

    可变参数模板使事情变得复杂,因为我不认为using Work&lt;JOBS&gt;::work...; 是合法的(而且我的编译器也不这么认为)。但是如果你“手动”组合基类,所有的工作方法都可以带入最终类:

    template <typename JOB, typename... JOBS>
    struct CollectWork : Work<JOB>, CollectWork<JOBS...> {
       using Work<JOB>::work;
       using CollectWork<JOBS...>::work;
    };
    
    template <typename JOB>
    struct CollectWork<JOB> : Work<JOB> {
       using Work<JOB>::work;
    };
    
    template<typename... JOBS>
    struct JobPlant : CollectWork<JOBS...> {                                           
       using CollectWork<JOBS...>::work;
       typedef Worker<JobPlant, JOBS...> WORKER;
       bool worker(const Job &job) { return WORKER()(this, job); }
    };
    

    有了这个结构,有问题的p-&gt;work(dynamic_cast&lt;const JOB &amp;&gt;(job));compiles and runs successfully

    【讨论】:

    • 嗯。那很简单。你能解释一下为什么调用p-&gt;work(dynamic_cast&lt;const JOB &amp;&gt;(job)) 会出现歧义错误吗?见:ideone.com/2vL57z
    • 用下面的代码替换 'p->Work::work()' 也解决了这个问题。工作 &w = *p; w.work(dynamic_cast(job));
    • @JVApen:你是对的!在调用work() 之前,我可以直接发送到右侧Work&lt;&gt;。然而,这个答案给我留下了深刻的印象,该答案展示了如何通过该方法进行调度,以及它对为什么天真的方法失败的解释。
    猜你喜欢
    • 2016-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-13
    • 2019-11-09
    • 2021-03-19
    相关资源
    最近更新 更多