一位同事今天问我看起来有点像这样的代码:
#include <iostream>
template <class T>
class IBase {
public:
virtual ~IBase() {}
public:
virtual void foo() = 0;
};
template <class T>
class Base : public IBase<T> {
public:
virtual void bar() {
foo(); // compiler error
}
};
class Derived : public Base<int> {
public:
virtual void foo() {
std::cout << "Hello World!\n";
}
};
int main() {
Derived d;
d.bar();
}
起初他收到编译错误,说没有找到“foo()
”。好的,所以他试图将其改为IBase<T>::foo();
。编译时,会导致链接器错误。所以我立即回忆起我之前见过这种类型的问题,并建议他写一下this->foo();
。中提琴!问题解决了!
然后他问我为什么不明白foo();
工作? this->x();
与x();
基本相同吗?老实说,我不知道,但他引起了我的兴趣。所以我们在这里:
总结:
virtual void bar() {
this->foo(); // works
//IBase<T>::foo(); // linker error
//foo(); // compiler error
}
问题是为什么需要this->
。为什么其他选项不起作用?
答案 0 :(得分:3)
因为基类成员是依赖名称 - 其含义取决于模板参数,因此在实例化模板之前不知道。在通用IBase
模板中没有查找该名称,因为在实例化之前,它可能是专门用于赋予它不同的含义。
使用IBase<T>::
对其进行限定非虚拟地调用基类函数;这通常不是你想要的,特别是如果(在这里)它是一个没有实现的纯虚函数。因此,尝试此时链接器错误。
使用this->
对其进行限定告诉编译器它是一个成员,并且任何进一步的检查都会延迟,直到模板被实例化为止。该功能仍被虚拟调用。
答案 1 :(得分:2)
想象一下你是编译器。您刚刚阅读并编译代码,现在您已达到bar
功能。在此函数中,您会看到尝试执行foo()
。此时,您知道foo
是什么吗?你没有。它可能来自基类,但你不可能知道,因为你还不知道T
是什么。当然IBase
可能存在foo
的特殊化,而this->
完全是其他的。
当您在函数调用之前粘贴this
时,它会使编译器将其视为依赖名称。这意味着编译器会说“好吧,这取决于foo
的类型,我还不知道。我会等到以后,当我知道如何实例化类时,我才会寻找{ {1}}“。
IBase<T>::foo();
给出了链接器错误,因为foo
中没有IBase<T>
的定义。
答案 2 :(得分:2)
#include <iostream>
template <class T>
class IBase {
public:
virtual ~IBase() {}
public:
virtual void foo() = 0;
};
int foo() { std::cout << "hello!\n"; }
template <class T>
class Base : public IBase<T> {
public:
virtual void bar() {
foo(); // which foo?!
}
};
template <>
class IBase<int> {
public:
virtual ~IBase() {}
//virtual void foo() = 0; -- no foo()!
};
class Derived : public Base<int> {
public:
virtual void foo() {
std::cout << "Hello World!\n";
}
};
int main() {
Derived d;
d.bar();
}
以上说明了为什么C ++不允许隐式找到依赖类型父项的成员。
当您在foo()
中致电Base
时,应该拨打foo()
? IBase<T>
中的那个或自由函数foo()
?
要么我们把决定关闭到以后,要么我们使用自由函数foo()
。
如果我们只使用自由函数foo()
,如果有一个可见,那么#include
订单中的细微更改可以大规模改变您的程序所做的事情。因此,如果它应该调用自由函数foo()
,那么如果找不到它,或者我们完全搞砸了它就必须出错。
如果我们将决定推迟到以后,则意味着可以解析和理解更少的template
直到更晚的日期。这会将更多错误移至实例化点。它也会导致一些令人惊讶的行为,比如上面的情况,有人可能会认为“我正在调用方法foo()
”,但实际上最终会调用自由函数foo()
而没有诊断。< / p>