这基本上是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);
}
答案 0 :(得分:9)
您明确调用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。