2014-03-06 39 views
1

的同事,今天问我关于代码看起来有点像这样:抽象基类调用父的纯虚函数

#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->。为什么其他选项不起作用?

回答

3

因为基类成员是一个从属名称 - 它的含义取决于模板参数,所以在模板实例化之前不知道。该名称在通用IBase模板中未查找,因为在实例化之前可能会专门给它赋予不同的含义。

IBase<T>::限定它调用基类函数非几乎;这通常不是你想要的,特别是如果(在这里)它是一个没有实现的纯虚函数。因此,当你尝试这个链接器错误。

this->限定它告诉它的成员的编译器,任何进一步的检查被推迟到模板实例。该功能仍然被称为虚拟。

+1

但'Base ::'也禁止虚拟调用语义,这就是为什么存在链接器错误。 –

+0

@SebastianRedl:好点,我没有想到通过正确。 –

2

想象一下,你是编译器。您刚刚阅读并编译代码,现在您已达到bar函数。在这个功能中,你会看到有人试图做foo()。在这一点上,你知道foo是什么吗?你没有。它可能来自基类,但你不可能知道,因为你不知道什么是T。当然可能有IBase专业化,其中foo是完全不同的东西。

当你的函数调用之前坚持this->,它会导致编译器把它作为一个从属名称。这意味着,编译器会说:“好吧,这取决于this的类型,我还不知道。我会等待,直到后来,当我知道类是如何被实例化,之前我找foo。”

IBase<T>::foo();给出了一个链接器错误,因为fooIBase<T>中根本没有定义。

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()而不进行诊断。