C++继承相关FAQ

算法琉璃客
• 阅读 1033

Is it okay to convert a pointer from a derived class to its base class? ¶ Δ

Yes.

An object of a derived class is a kind of the base class. Therefore the conversion from a derived class pointer to a base class pointer is perfectly safe, and happens all the time. For example, if I am pointing at a car, I am in fact pointing at a vehicle, so converting a Car to a Vehicle is perfectly safe and normal:

void f(Vehicle* v);
void g(Car* c) { f(c); }  // Perfectly safe; no cast

(Note: this FAQ has to do with public inheritance; private and protected inheritance are different.)

  • C++的public继承是is-a的关系,私有和保护继承与上面的不同

What’s the difference between public, private, and protected?

  • A member (either data member or member function) declared in a private section of a class can only
    be accessed by member functions and friends of that class
  • A member (either data member or member function) declared in a protected section of a class can only
    be accessed by member functions and friends of that class, and by member functions and friends of derived classes
  • A member (either data member or member function) declared in a public section of a class can be accessed by anyone
  • 只有公共成员能在类外被直接
  • 保护和私有成员只能被成员函数/友元访问,保护级别还能被子类和子类的友元访问

Why can’t my derived class access private things from my base class? ¶ Δ

  • To protect you from future changes to the base class.
  • Derived classes do not get access to private members of a base class. This effectively “seals off” the derived class from any changes made to the private members of the base class.
  • 子类不能访问父类的私有成员

private-inheritance

What are the access rules with private and protected inheritance?

Take these classes as examples:

    class B                    { /*...*/ };
    class D_priv : private   B { /*...*/ };
    class D_prot : protected B { /*...*/ };
    class D_publ : public    B { /*...*/ };
    class UserClass            { B b; /*...*/ };

None of the derived classes can access anything that is private in B. In D_priv, the public and protected parts of B are private. In D_prot, the public and protected parts of B are protected. In D_publ, the public parts of B are public and the protected parts of B are protected (D_publ is-a-kind-of-a B). class UserClass can access only the public parts of B, which “seals off” UserClass from B.

  • 在子类中不能访问父类的私有成员
  • 私有继承中父类的公有和保护成员都成为子类的私有成员(可以在这一个子类的成员函数/友元中访问)
  • 保护继承中父类的公有和保护成员都成为子类的保护成员(可以被子类/当前继承了父类的子类/友元中访问)
  • 在公有继承中,父类中的公有成员依然是公有成员,保护成员依然是保护成员(对于这两个使用上面的规则)
  • 包含的时候,从一个B的实例中只能访问b的公共的部分
  • 可以使一个B中的公有成员在保护/私有继承的子类中依然公有,使用using B::f这种语句

To make a public member of B public in D_priv or D_prot, state the name of the member with a B:: prefix. E.g., to make member B::f(int,float) public in D_prot, you would say:

    class D_prot : protected B {
    public:
      using B::f;  // Note: Not using B::f(int,float)
    };

When my base class’s constructor calls a virtual function on its this object, why doesn’t my derived class’s override of that virtual function get invoked?

  • ref https://stackoverflow.com/que...
  • 尽量不要在ctor/dtor调用虚函数,此时虚函数表现的和非虚函数一样,会调用当前指针所表示的平凡版本
  • 不表现出动态绑定的特性是因为此时子类可能还未初始化/子类部分已经被析构掉了

Because that would be very dangerous, and C++ is protecting you from that danger.

The rest of this FAQ gives a rationale for why C++ needs to protect you from that danger, but before we start that, be advised that you can get the effect as if dynamic binding worked on the this object even during a constructor via The Dynamic Binding During Initialization Idiom.

You can call a virtual function in a constructor, but be careful. It may not do what you expect. In a constructor, the virtual call mechanism is disabled because overriding from derived classes hasn’t yet happened. Objects are constructed from the base up, “base before derived”.

Consider:

    #include<string>
    #include<iostream>
    using namespace std;
    class B {
    public:
        B(const string& ss) { cout << "B constructor\n"; f(ss); }
        virtual void f(const string&) { cout << "B::f\n";}
    };
    class D : public B {
    public:
        D(const string & ss) :B(ss) { cout << "D constructor\n";}
        void f(const string& ss) { cout << "D::f\n"; s = ss; }
    private:
        string s;
    };
    int main()
    {
        D d("Hello");
    }

the program compiles and produce

    B constructor
    B::f
    D constructor

Note not D::f. Consider what would happen if the rule were different so that D::f() was called from B::B(): Because the constructor D::D() hadn’t yet been run, D::f() would try to assign its argument to an uninitialized string s. The result would most likely be an immediate crash. So fortunately the C++ language doesn’t let this happen: it makes sure any call to this->f() that occurs while control is flowing through B’s constructor will end up invoking B::f(), not the override D::f().

Destruction is done “derived class before base class”, so virtual functions behave as in constructors: Only the local definitions are used – and no calls are made to overriding functions to avoid touching the (now destroyed) derived class part of the object.

For more details see D&E 13.2.4.2 or TC++PL3 15.4.3.

It has been suggested that this rule is an implementation artifact. It is not so. In fact, it would be noticeably easier to implement the unsafe rule of calling virtual functions from constructors exactly as from other functions. However, that would imply that no virtual function could be written to rely on invariants established by base classes. That would be a terrible mess.

What’s the meaning of, Warning: Derived::f(char) hides Base::f(double)? ¶ Δ

  • 函数声明的覆盖 包括虚函数与非虚函数

It means you’re going to die.

  • 如果基类声明一个函数,子类也声明一个同名函数(只是参数类型/常量性不同),那么基类声明的函数会被隐藏而非重载/重写(即使基类的f(double)是虚函数)

Here’s the mess you’re in: if Base declares a member function f(double x), and Derived declares a member function f(char c) (same name but different parameter types and/or constness), then the Base f(double x) is “hidden” rather than “overloaded” or “overridden” (even if the Base f(double x) is virtual).

    class Base {
    public:
      void f(double x);  // Doesn't matter whether or not this is virtual
    };
    class Derived : public Base {
    public:
      void f(char c);  // Doesn't matter whether or not this is virtual
    };
    int main()
    {
      Derived* d = new Derived();
      Base* b = d;
      b->f(65.3);  // Okay: passes 65.3 to f(double x)
      d->f(65.3);  // Bizarre: converts 65.3 to a char ('A' if ASCII) and passes it to f(char c); does NOT call f(double x)!!
      delete d;
      return 0;
    }

Here’s how you get out of the mess: Derived must have a using declaration of the hidden member function. For example,

    class Base {
    public:
      void f(double x);
    };
    class Derived : public Base {
    public:
      using Base::f;  // This un-hides Base::f(double x)
      void f(char c);
    };

If the using syntax isn’t supported by your compiler, redefine the hidden Base member function(s), even if they are non-virtual. Normally this re-definition merely calls the hidden Base member function using the :: syntax. E.g.,

    class Derived : public Base {
    public:
      void f(double x) { Base::f(x); }  // The redefinition merely calls Base::f(double x)
      void f(char c);
    };

Note: the hiding problem also occurs if class Base declares a method f(char).

Note: warnings are not part of the standard, so your compiler may or may not give the above warning.

在你将一个基类指针指向一个子类的对象的时候,基类的函数不会被隐藏,因为编译器无法假定它会指向某一个具体的子类
Note: nothing gets hidden when you have a base-pointer. Think about it: what a derived class does or does not do is irrelevant when the compiler is dealing with a base-pointer. The compiler might not even know that the particular derived class exists. Even if it knows of the existence of some particular derived class, it cannot assume that a specific base-pointer necessarily points at an object of that particular derived class. Hiding takes place when you have a derived pointer, not when you have a base pointer.

Why doesn’t overloading work for derived classes? 为什么函数重载在子类不起作用?

That question (in many variations) are usually prompted by an example like this:

    #include<iostream>
    using namespace std;
    class B {
    public:
        int f(int i) { cout << "f(int): "; return i+1; }
        // ...
    };
    class D : public B {
    public:
        double f(double d) { cout << "f(double): "; return d+1.3; }
        // ...
    };
    int main()
    {
        D* pd = new D;
        cout << pd->f(2) << '\n';
        cout << pd->f(2.3) << '\n';
        delete pd;
    }

which will produce:

    f(double): 3.3
    f(double): 3.6

rather than the

    f(int): 3
    f(double): 3.6

that some people (wrongly) guessed.

换句话说,根本就不存在DB之间的重载决议,重载决议每次只在一个作用域起作用:编译器在D类的作用域找到了double f(double),然后就调用这个函数,
因为在D中就找到了一个匹配,编译器不会再去B的作用域寻找.C++中不存在跨作用域的函数重载,子类的作用域也不例外.

下面是我自己的理解:
    按照[上一条](https://isocpp.org/wiki/faq/strange-inheritance#hiding-rule)的理解,子类的重名的函数会覆盖父类的函数(不管是否是虚函数)
    此时如果在`D`中没有合适的匹配,编译器会报错

    在另一种情况下,子类没有覆盖父类的名字,此时在子类中找不到这个名字的话,编译器会去父类的作用域去寻找这个名字

In other words, there is no overload resolution between D and B. Overload resolution conceptually happens in one scope at a time:
The compiler looks into the scope of D, finds the single function double f(double), and calls it. Because it found a match, it never
bothers looking further into the (enclosing) scope of B. In C++, there is no overloading across scopes – derived class scopes are
not an exception to this general rule. (See D&E or TC++PL4 for details).

But what if I want to create an overload set of all my f() functions from my base and derived class? That’s easily done using a using-declaration, which asks to bring the functions into the scope:

    class D : public B {
    public:
        using B::f; // make every f from B available
        double f(double d) { cout << "f(double): "; return d+1.3; }
        // ...
    };

Given that modification, the output will be:

    f(int): 3
    f(double): 3.6

That is, overload resolution was applied to B’s f() and D’s f() to select the most appropriate f() to call.

C++可见性

小时候,我一度以为这样的代码是不合法的。

// 在foo类的成员函数中访问foo的私有成员
class foo { int a; public: int foobar(foo * f) { return this->a + f->a; } };

因为我担心在 foo::foobar 中不能访问 f 的私有成员变量 a。

后来我明白了,所谓私有,是针对类的,而不是具体的对象。

class foo { protected: int a; }; class foobar : public foo { public: int bar(foo * f) { return this->a + f->a; } };

这次,在 foobar::bar 里,访问 this 的 a 成员允许,但 f 的 a 成员却被禁止了。

因为 foo::a 对 foobar 是 protected 的,foobar 的成员函数可以访问自己的 a ,但是对于 foo 指针,就禁止了。


我的想法:

这里`bar`函数是`foobar`的成员函数,而不是foo的成员函数,因此不能使用`f->a`的方式获取其保护/私有成员

想了一下,解决方案是。

class foo { protected: int a; static int get_a(foo *self) { return self->a; } }; class foobar : public foo { public: int bar(foo * f) { return this->a + get_a(f); } };

很坏味道。不过也不太所谓了。

这里就是定义一个静态的成员函数来在foo的静态成员函数中取出这个值

What’s the difference between the keywords struct and class? struct和class之间有什么区别?

结构体的成员和基类默认是public的,类中默认是私有的
但是即使这样,在类中/继承时依然应该显式的声明出来,而非依靠隐式的默认值.
The members and base classes of a struct are public by default, while in class, they default to private. Note: you should make your base classes explicitly public, private, or protected, rather than relying on the defaults.

除此之外,两者在功能性上是等同的.
struct and class are otherwise functionally equivalent.

Enough of that squeaky clean techno talk. Emotionally, most developers make a strong distinction between a class and a struct. A struct simply feels like an open pile of bits with very little in the way of encapsulation or functionality. A class feels like a living and responsible member of society with intelligent services, a strong encapsulation barrier, and a well defined interface. Since that’s the connotation most people already have, you should probably use the struct keyword if you have a class that has very few methods and has public data (such things do exist in well designed systems!), but otherwise you should probably use the class keyword.

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
1年前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
HTTP面试题(二):HTTP请求报文和响应报文格式
!(https://oscimg.oschina.net/oscnet/0406894fb1274bee91fc53c84c516576.jpg)看都看了还不点个赞!(https://oscimg.oschina.net/oscnet/095d444dc9a449ee85afd19b00fdf52b.png)!(h
Stella981 Stella981
3年前
Duang,HUAWEI DevEco IDE全面升级啦
想感受全新UI带来的视觉及交互体验、HiKey970开发板调测、HiAIAPI推荐和收藏、深度AI模型分析等新功能,体验高清晰度和流畅度的远程AI真机调测吗?!(https://oscimg.oschina.net/oscnet/f4e1bb24ff00b8c6ea27f75370a53bfbacd.jpg)全新的UI设计
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这