关于C++:从派生类对象调用基类方法

Call base class method from derived class object

如何从派生类对象调用由派生类重写的基类方法?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base{
  public:
    void foo(){cout<<"base";}
};

class Derived:public Base{
  public:
    void foo(){cout<<"derived";}
}

int main(){
  Derived bar;
  //call Base::foo() from bar here?
  return 0;
}


通过使用限定ID,可以始终(*)引用基类的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

class Base{
  public:
    void foo(){std::cout<<"base";}
};

class Derived : public Base
{
  public:
    void foo(){std::cout<<"derived";}
};

int main()
{
  Derived bar;
  //call Base::foo() from bar here?
  bar.Base::foo(); // using a qualified-id
  return 0;
}

[也修正了一些操作错误]

(*)访问限制仍然适用,基类可能不明确。

如果Base::foo不是virtual,那么Derived::foo不会覆盖Base::foo。相反,Derived::foo隐藏了Base::foo。在下面的示例中可以看到差异:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Base {
   void foo()         { std::cout <<"Base::foo
"
; }
   virtual void bar() { std::cout <<"Base::bar
"
; }
};

struct Derived : Base {
   void foo()         { std::cout <<"Derived::foo
"
; }
   virtual void bar() { std::cout <<"Derived::bar
"
; }
};

int main() {
    Derived d;
    Base* b = &d;
    b->foo(); // calls Base::foo
    b->bar(); // calls Derived::bar
}

(即使不使用virtual关键字,只要签名与Base::bar兼容,Derived::bar也是隐式虚拟的。)

限定ID的形式可以是X :: Y或只是:: Y::前面的部分指定了我们要在哪里查找标识符Y。在第一种形式中,我们查找X,然后从X的上下文中查找Y。在第二种形式中,我们在全局名称空间中查找Y

非限定ID不包含::,因此不(本身)指定查找名称的上下文。

在表达b->foo时,bfoo都是不合格的ID。在当前上下文中查找b(在上面的示例中是main函数)。我们找到局部变量Base* b。由于b->foo具有类成员访问的形式,我们从b类型(或者更确切地说是*b类型)的上下文中查找foo。所以我们从Base的上下文中查找foo。我们将找到在Base中声明的成员函数void foo(),我称之为Base::foo

对于foo,我们现在已经完成了,我们称之为Base::foo

对于b->bar,我们首先找到Base::bar,但它被宣布为virtual。因为它是virtual,所以我们执行虚拟调度。这将调用对象b指向的类层次结构中的最终函数重写器。因为b指向Derived类型的对象,所以最后一个重写器是Derived::bar

Derived的上下文中查找foo的名称时,我们会找到Derived::foo。这就是为什么据说Derived::foo隐藏了Base::foo的原因。如d.foo()Derived的成员函数内,仅使用foo()this->foo()的表达式将从Derived的上下文中查找。

当使用限定ID时,我们显式地声明在何处查找名称的上下文。表达式Base::foo说明我们要从Base的上下文中查找名称foo(例如,它可以找到Base继承的函数)。此外,它禁用虚拟调度。

因此,d.Base::foo()会找到Base::foo并称之为;d.Base::bar()会找到Base::bar并称之为。

有趣的事实:纯虚拟函数可以有一个实现。不能通过虚拟调度调用它们,因为它们需要被重写。但是,您仍然可以使用限定ID调用它们的实现(如果它们有实现的话)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>

struct Base {
    virtual void foo() = 0;
};

void Base::foo() { std::cout <<"look ma, I'm pure virtual!
"
; }

struct Derived : Base {
    virtual void foo() { std::cout <<"Derived::foo
"
; }
};

int main() {
    Derived d;
    d.foo();       // calls Derived::foo
    d.Base::foo(); // calls Base::foo
}

请注意,类成员和基类的访问说明符都会影响您是否可以使用限定ID在派生类型的对象上调用基类的函数。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>

struct Base {
public:
    void public_fun() { std::cout <<"Base::public_fun
"
; }
private:
    void private_fun() { std::cout <<"Base::private_fun
"
; }
};

struct Public_derived : public Base {
public:
    void public_fun() { std::cout <<"Public_derived::public_fun
"
; }
    void private_fun() { std::cout <<"Public_derived::private_fun
"
; }
};

struct Private_derived : private Base {
public:
    void public_fun() { std::cout <<"Private_derived::public_fun
"
; }
    void private_fun() { std::cout <<"Private_derived::private_fun
"
; }
};

int main() {
    Public_derived p;
    p.public_fun();        // allowed, calls Public_derived::public_fun
    p.private_fun();       // allowed, calls Public_derived::public_fun
    p.Base::public_fun();  // allowed, calls Base::public_fun
    p.Base::private_fun(); // NOT allowed, tries to name Base::public_fun

    Private_derived r;
    r.Base::public_fun();  // NOT allowed, tries to call Base::public_fun
    r.Base::private_fun(); // NOT allowed, tries to name Base::private_fun
}

可访问性与名称查找是正交的。因此,名称隐藏不会对其产生影响(您可以在派生类中省略public_funprivate_fun,并为限定ID调用获得相同的行为和错误)。

p.Base::private_fun()中的错误与r.Base::public_fun()中的错误不同:第一个错误已经没有引用Base::private_fun的名称(因为它是一个私有名称)。第二个无法将rPrivate_derived&转换为Base&,用于this指针(本质上)。这就是为什么第二个是在Private_derivedPrivate_derived的朋友那里工作的。


首先,派生的应该从基继承。

1
 class Derived : public Base{

那说

首先,你们不能在派生的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Base{
  public:
    void foo(){cout<<"base";}
};

class Derived : public Base{

}

int main(){
  Derived bar;
  bar.foo() // calls Base::foo()
  return 0;
}

其次,您可以使派生::foo调用base::foo。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Base{
  public:
    void foo(){cout<<"base";}
};

class Derived : public Base{
  public:
    void foo(){ Base::foo(); }
                ^^^^^^^^^^
}

int main(){
  Derived bar;
  bar.foo() // calls Base::foo()
  return 0;
}

第三,可以使用base::foo的限定ID

1
2
3
4
5
 int main(){
    Derived bar;
    bar.Base::foo(); // calls Base::foo()
    return 0;
 }


首先考虑使foo()虚拟化。

1
2
3
4
5
6
7
8
9
10
11
class Base {
public:
    virtual ~Base() = default;

    virtual void foo() {}
};

class Derived : public Base {
public:
    virtual void foo() override {}
};

但是,这样做的目的是:

1
2
3
4
5
int main() {
    Derived bar;
    bar.Base::foo();
    return 0;
}


一个重要的[附加]注意:如果发生名称隐藏,您仍然会有编译错误。

在这种情况下,要么使用using关键字,要么使用qualifer。另外,也可以看到这个答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>

class Base{
  public:
    void foo(bool bOne, bool bTwo){std::cout<<"base"<<bOne<<bTwo;}
};

class Derived : public Base
{
  public:
    void foo(bool bOne){std::cout<<"derived"<<bOne;}
};

int main()
{
  Derived bar;
  //bar.foo(true,true);      // error:    derived func attempted
  bar.foo(true);             // no error: derived func
  bar.Base::foo(true,true);  // no error: base func, qualified
  return 0;
}