抽象基类调用父的纯虚函数

时间:2014-03-06 16:26:32

标签: c++ templates inheritance virtual-functions

一位同事今天问我看起来有点像这样的代码:

#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 个答案:

答案 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>