这本质上是 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
。 JobPlant
继承自 Work
,用于 JOBS
中的每个作业类型。 MyJobPlant
是 JobPlant
的一个实例,并从关联的 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/