c++ - 从派生的可变参数模板类调用基模板的虚方法

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

这本质上是 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 来执行jobJobPlant 继承自 Work,用于 JOBS 中的每个作业类型。 MyJobPlantJobPlant 的一个实例,并从关联的 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);
}

最佳答案

您明确调用 p->Work<JOB>::work() ,即Work<JOB>中的纯虚方法.这个方法没有实现(毕竟是纯粹的)。

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

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


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

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

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

可变参数模板使事情变得复杂,因为我不认为 using Work<JOBS>::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->work(dynamic_cast<const JOB &>(job)); compiles and runs successfully .

关于c++ - 从派生的可变参数模板类调用基模板的虚方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26374116/

相关文章:

c++ - 带有可变参数模板的标记分派(dispatch),c++11

c++ - 前进或 move

c++ - 如何确定 NifTi 文件的字节顺序?

c++ - 使用仿函数模板或继承库

c++ - 在 C++ 中相互调用的模板方法

c++ - 检查是否存在全局(/命名空间)函数/对象声明

c++ - 自动生成的 move 操作和原始指针成员

c++ - 关于虚拟方法的问题

c++ - 优化 C++ 项目的源代码结构

c++ - 我的 Z 缓冲区计算有什么问题?