缘由
下面一段小程序的报错:
#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继承,则无改问题。
疑问及延伸问题点
- c++的对象模型中是否包含private、protected、public这些访问修饰符的信息?
- c++是如何保证访问修饰符的行为正确的落实下去?
- 这样设计的原因是什么呢?
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 |