深入理解訪問修飾符(private、protected、public)

緣由

下面一段小程序的報錯:

#include <iostream>
using namespace std;

class Base {
public:
    explicit Base(int x):x(x){};
    int x;
};

class Derived : Base {
public:
    Derived():Base(10), y(1) {
      cout << "Derived construct." << endl;
    };
    int y;
};

int main()
{
    Derived derived;
    Base *base = &derived;
    return 0;
}

報錯如下:

D:\CLionProjects\Demo\demo.cpp: In function ‘int main()’:
D:\CLionProjects\Demo\demo.cpp:25:19: error: ‘Base’ is an inaccessible base of ‘Derived’
Base *base = &derived;

先說原因:
首先先看編譯報錯:Base 是Derived的一個不可訪問的基類,也就是Derived的對象derived中包含的Base對象是私有的(c++中默認的繼承關係是private的,類中的成員默認也是private),那麼按照private的設計,derived的對象是無法訪問Base的成員的,那麼再將父類的指針指向子類的對象,父類指針是不能做任何操作的(均爲非法的)。
修改:
將繼承關係改爲public繼承,則無改問題。

疑問及延伸問題點

  1. c++的對象模型中是否包含private、protected、public這些訪問修飾符的信息?
  2. c++是如何保證訪問修飾符的行爲正確的落實下去?
  3. 這樣設計的原因是什麼呢?

c++的對象模型中是否包含private、protected、public這些訪問修飾符的信息

在上一篇子類初始化列表不能初始化父類元素也大致描述了對象模型,如下:
在這裏插入圖片描述
可以看到子類的對象中首先包含了父類對象的內容,那麼在內存模型中是否存放了private這些訪問修飾符的信息呢(或者private的變量直接創建到內存中);我們來看下不同繼承關係下子類對象的大小的情況:
private 繼承

// 按照上面的例子做些許修改,定義類時加上alignas(4),保證是4字節對齊。
class alignas(4) Base {
public:
    explicit Base(int x):x(x){};
    int x;
};

class alignas(4) Derived :  Base {
public:
    Derived():Base(10), y(1){
      cout << "Derived construct." << endl;
    };
    int y;
};

int main()
{
    Derived derived;
    //Base *base = &derived;
    cout << "derived's size: " << sizeof(derived) << endl;

    return 0;
}

輸出

Derived construct.
derived’s size: 8

將class alignas(4) Derived : Base 修改爲class alignas(4) Derived : public Base
輸出

Derived construct.
derived’s size: 8

輸出相同,則private和public不會對不同訪問修飾符的變量進行裁剪;根據對象的大小爲8:= sizeof(x) + sizeof(y),那麼不管訪問修飾符是什麼,都會創建到c++的對象內存模型中;
那麼答案也就是:c++的對象模型跟訪問修飾符無關,都會將成員創建到對應的內存中去。

c++是如何保證訪問修飾符的行爲正確的落實下去

是否通過內存模型來進行保證了,其實上面已經說明了,沒關係;大家還可以看下下面的一個有趣的例子(輔助證明無關)

class Test {
private:
    int x = 2;
    int y = 3;
};

int main()
{
	Test t;
    cout << "t.x: " << *((int *)(&t)) << endl;
    cout << "t.y: " << *(((int *)(&t))+1) << endl;
}

輸出:

t.x: 2
t.y: 3

哈哈,我們可以訪問private變量了;側面說明,通過內存直接訪問是不受訪問修飾符影響的;
那它受什麼影響(或者說保障)呢?
編譯器行爲,或者說語言設計如此;既然對象內存模型沒有區分訪問修飾符,語言設計又是如此,那麼如何保障呢,編譯器,在編譯階段,指定規則使之滿足語言設計的要求。

這樣設計的原因是什麼呢

回頭想想,訪問修飾符是對C++的封裝、抽象的OOP原理而設計的;那麼誰來保證這個呢,如果將訪問修飾符記入內存中,面臨的問題是如何記錄,內存增大、與C語言的兼容性等等問題;所以這部分交由給編譯器去做是最合適的(並且這些內容在編譯器就能夠發現);一種語言 + 一種編譯器 + 一種運行環境,軟件從無到有;能儘早發現的問題,儘早解決。

訪問修飾符效果

Keyword C# C++ Java
‘’‘private’’’ class class’‘and/or’'friend classes class
‘’‘private protected’’’ derived classes in the same assembly - -
‘’‘protected internal’’’ same assembly
’‘and/or’'
derived classes
- -
‘’‘protected’’’ derived classes derived classes’‘and/or’'friend classes derived classes
’‘and/or’'
within same package
‘’‘package’’’ - - within its package
‘’‘internal’’’ same assembly - -
‘’‘public’’’ everybody everybody everybody

繼承時:

Access specifier in base class Access specifier when inherited publicly
Public Public
Private Inaccessible
Protected Protected
Access specifier in base class Access specifier when inherited privately
Public Private
Private Inaccessible
Protected Private
Access specifier in base class Access specifier when inherited protectedly
Public Protected
Private Inaccessible
Protected Protected
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章