« 我诅咒帮网易做 OA 系统的公司 | 返回首页 | C 语言的数据序列化 »

C++ 中的 protected

当我还在用 C++ 做主要开发语言的最后几年,我已经不大用 protected 了。从箱底翻出曾经钟爱的一本书:《C++语言的设计和演化》,中文版 235 页这样记录:

“ ... Mark Linton 顺便到我的办公室来了一下,提出了一个使人印象深刻的请求,要求提供第三个控制层次,以便能支持斯坦福大学正在开发的 Interviews 库中所使用的风格。我们一起揣测,创造出单词 protected 以表示类里的一些成员,...”

“... Mark 是 Interviews 的主要设计师。他的有说服力的争辩是基于实际经验和来自真实代码的实例。...”

“...大约五年之后,Mark 在 Interviews 里禁止了 protected 数据成员,因为它们已经变成许多程序错误的根源...”

我不喜欢 protected ,但是今天,我偶尔用一下 C++ 时,不再有那么多洁癖。反正很难用 C++ 做出稳定的设计,那么,爱怎么用就怎么用吧。关键是别用 C++ 做特别核心的东西就成了。

今天,碰到一个跟 protected 有关的问题,小郁闷了一下。觉得可以写写。这个倒是个基本问题,貌似以前很熟悉。毕竟很多年不碰了,对 C++ 语法有点生疏。

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

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 指针,就禁止了。

想了一下,解决方案是。

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); } };

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

Comments

@cloud
这样本来是不能访问的,C++ Primer 4th 15.2.2 有提到protected的另一重要性质:派生类对其基类类型的对象protected成员没有特殊访问权限。

OO更希望别人从抽象封装的层面来看待和组织程序,它着重于抽象的概念和设计.泛化权限只是用来描述和约束类之间的泛化关系,这和对象之间的通讯没有本质的矛盾.看待对象之间的链接你可以认为是方法调用或者纯消息传递,问题的关键在于你怎么看待.c++提供的泛化约束当然是不可能满足像C一样的无约束的自由,矛盾在看待问题的层面不同.泛化约束和对象间的通讯本质上是不同的问题.而不是语言设计上的问题.

— protected; that is, its name can be used only by members and friends of the class in which it is
declared, and by members and friends of classes derived from this class

C++ ISO 14882 p179

@桂林旅游 :
你他妈的也太操蛋了吧!这样都行?

@桂林旅游 :
你他妈的也太操蛋了吧!这样都行?

@桂林旅游 :
你他妈的也太操蛋了吧!这样都行?

博客主真的好强呀,对我来说C++语言太难了

呃……什么情况下需要用到这样的访问……

两个Object之间的关系这个问题,我也和同事是讨论过多次的:)

C++的设计是有问题的,它作了太多的妥协,现在看是Out了(其实我不够资格评价)。

C++ 的设计不能简单的说有没有问题,只能说看起来更像个半成品,因为 OO 的思想就是个半成品。OO 过分强调了 Object 这个概念,而忽略了作为一个完整系统运作起来的另一个必要条件,Object 和 Object 之间的关系。

Object 的封装这个策略或者说方法论本身没错,但是目前为止我见到的书和人在描述 OO 的时候,无一不是只强调 Object 本身,而无视了 Object 与 Object 之间关系的,他们所有的描述都围绕着 Object 这一个概念在打转,各种讲解 OOP 的书和教程在展示例子代码的时候几乎都充斥了无数的将关系封装进 Object 而不自知的行为。

正确的 OO 封装,应该是除了自身数据成员之外完全不涉及到另外一个 Object,即使是本类的另一个实例也不行。

举个明显点的例子,一个树结构连接父节点和子节点的操作,跟LZ文章里的代码有点类似,这明显是一种关系,两个独立的节点之间的关系。在设计 Object 的时候,有3种方案:第一种,this作为父节点,封装一个这样的方法:Node::Add( Node* child );第二种,this作为子节点,封装方法:Node::AttachTo( Node* parent );第三种,static Node::Attach( Node* parent, Node* child )。

在设计者以 OOP 思想对 Object 进行设计的时候通常会倾向于在第一二种方案里选择一种,但是第一二种其中的任何一种设计对另外一种来说都是“不公平”的,你能简单的说第一种好过第二种或者第二种好过第一种么?而且刚接触 OOP 的人很可能会因为选择两种方案的哪一种犯难:只实现第一种还是只实现第二种?还是两种全部加上?两种全部加上之后你就会发现代码可能会有重复,然后又有3种解决方案:一call二;二call一;一二call一个共同的static。最后提出一个static那不是就又绕到第三种方案去了么?

但是对习惯使用 C 语言的思想来解决问题的人就太简单了,第三种方案在他们眼中就是完美的解决方法。不要跟我说 thiscall 用一个寄存器传递指针能快多少多少,这东西是根据平台和编译器实现而有差别的,在有些平台上所有的参数全部是用寄存器传递的,这种细节问题不是重点,也不能拿来作为比较的依据。
如果你实在是需要那个 OOP 的形式了不起加上第一二种方法的一个空壳,在里面直接调用第三种的 static 方法,最后的结果也是一样。

上面的解决问题的两条路看起来都差不多,最后的结果都一样,或许很多人最后都会做到这一步,但是意义却是不一样的。简单来说,前面一种情况是从 OO 的思想出发的,后面的一种则一开始就考虑到了 Object 之间的关系的。

很多人在用 OOP 做设计的时候都是走的第一条路,而且还不见得人人都可以走到最后,其中甚至不乏项目经理产品经理之类的高管人员,最后他们大骂 C++ 或者他们正在使用的 OOP 编程语言是渣然后或者把这问题丢给了下面的具体负责实现的人去头痛,或者随便在第一二种方案里选了一种,或者就有干脆把 C++ 枪毙了换上 C 的。

对于 protected 这个东西,在我看来它就是一个半成品,它本身产生的约束不能说完全没有意义,但是要能够将它驾驭好是需要一定的功力的,因为你无法预测到所有派生类的需求,而这个机制实际上起到的并不是如它名字一样的“保护”作用,而是在制定派生类在进行派生的时候必须遵循的某种规则——这才是 protected 的本质。因此可以说,这个机制在“表达 OOP 的设计思想和结构”上的意义多过它在功能上的意义。

好的设计者完全能够做到即使没有 protected 这个东西一样不会让各种成员的使用产生混乱,甚至连 private 和 public 都不需要——完全就是 C 的风格了,这也是很多人会觉得 C 用起来比 C++ 更舒坦,不会有被绑手绑脚的感觉的原因之一。

C 可以做到如 C++ 一般的 OO,但是 C++ 的 OO 却做不到 C 能够做到的你可以想到的和你还未曾想到的不同于现今已有的 OO 的“另外一种 OO”。

《C++语言的设计和演化》,看得好累。

C++的设计是有问题的。

c++是好东西,博主理解的很好 呵呵

今天偶然翻到这篇文章,可以参考一下:
http://blog.vckbase.com/bruceteen/archive/2008/04/24/33375.html

错了,是11楼的亮哥,呵呵

感谢11楼的 ameidai傻瓜版,学习了,呵呵!

他们是两个不同的类,是符合语义的!不应该能访问foo的protected成员啊

to flair
和你的理解一样!

云风这样的境界最近喜欢用什么语言呢?

可不可以如此理解:foobar和foo是完全不同的类,虽然他们有继承关系,但仍然是不同的类,不能在一个类中去调用另一个和自己类不同的非友元的非public成员.

从设计对称的角度上来看,我想说这是c++的一个缺陷,这样的缺陷可能还不少……

要注意休息,身体很重要!

略作修改,同时改进一下格式,方便大家阅读
我去做了一下试验,总结了一下:
1 private 的成员,只能用在本类(派生类不是本类)成员函数中,(当然也包括本类的另一个对象的直接“."操作)。
例子:
class foo

{
private:
int a;
public:
void func(foo *f)
{
f->a = 1; //compile success
}
};

class foobar : public foo

{
public:
int bar(foo * f)

{
a ; //compile error
f->a; //compile error
return 0;
}
};

2 protected
包含private的功能,另外,这个类的派生类的成员涵数中也能直接用这个数据成员,但是在这个派生类的涵数里面,却不能使用基类去操作这个成员了。比如:
class foo
{
protected:
int a;
public:
void func(foo *f)
{
f->a = 1; //compile success
}
};

class foobar : public foo
{
public:
int bar(foo * f)
{
a ; //compile success
f->a; //compile error
return 0;
}
int bar1(foobar * f)
{
return f->a; //compile success;
}
};
对此,我是这么理解的,protected成员是可以继承下来,继承下来了后,在编写派生类时,他的获取权限是属于这个派生类了,再也不属于基类了,就可套用private规则:只适用于本类,其它的类(包括基类)都不能直接操作。似乎这么去记忆就没有问题了,不会搞混了

我又来发炎了,我在 网上搜了一下,有这么一段文字:
A member of a class can be

— private; that is, its name can be used only by members and friends of the class in which it is declared.
— protected; that is, its name can be used only by members and friends of the class in which it is declared, and by
members and friends of classes derived from this class (see 11.5).
— public; that is, its name can be used anywhere without access restriction.
我去做了一下试验,总结了一下:
1 private 的成员,只能用在本类(派生类不是本类)成员函数中,(当然也包括本类的另一个对象的直接“."操作)。
例子:
class foo {
private:
int a;
public:
void func(foo *f)
{
f->a = 1; //compile success
}
};

class foobar : public foo {
public:
int bar(foo * f) {
a ; //compile error
f->a; //compile error
return 0;
}
};

2 protected
包含private的功能,另外,这个类的派生类的成员涵数中也能直接用这个数据成员,但是在这个派生类的涵数里面,却不能使用基类去操作这个成员了。比如:
class foo
{
protected:
int a;
public:
void func(foo *f)
{
f->a = 1; //compile success
}
};

class foobar : public foo
{
public:
int bar(foo * f)
{
a ; //compile success
f->a; //compile error
return 0;
}
int bar1(foobar * f)
{
return f->a; //compile success;
}
};
对此,我是这么理解的,protected成员是可以继承下来,继承下来了后,他的获取权限是属于这个派生类了,再也不属于基类了,就可套用private规则,只适用于本类,其它的类(包括基类)都不能直接操作。

@acumon

此文想说明的问题正在于那个坏味道的 static getter

private 数据成员,protected getter 并不解决问题:让非此家族系的地方都访问不到这个成员,而让本家族的都可以。

相对而言, public getter 与 public data member 差异就比较小了。

或许 涛 举的例子更真实一些。MFC 的 CWnd 的窗口句柄数据成员m_hWnd 是 public 的。

No foo-child. I am sorry.

感觉你有句话是错误的:
/***Begin******/
后来我明白了,所谓私有,是针对类的,而不是具体的对象。
/***End******/
看下面的代码:
#include <iostream>
class foo {
private:
int a;
public:
foo()
{
a = 10;
}
int foobar(foo f) {
return this->a + f.a;
}
int foobar(foo* f)
{
return this->a + f->a;
}
};

int main()
{
foo k;
int ret0 = k.a; //Compile error
std::cout << ret0 << "";
int ret = k.foobar(k);
std::cout << ret << "";
}
所以,我觉得
所谓私有,是类和类的具体的对象的。
而且在另一个类中新类中(非继承),也是不可以访问foo对象实例的私有member的。这个很容易验证。。。

但是为什么可以在foo和foo-child中可以访问私有member的原因就不知道了。

如果非要猜测的话可能是由于friend的缘故:是不是编译器默认会把自己作为自己的friend???

云风大哥,MFC中类CWnd的窗口句柄数据成员m_hWnd定义成public的,而没有定义成protected,是不是也是因为不好用? 学习C++的时候,书上都说不要把数据成员公有化,MFC里却这么干了。

不得不说在对象访问权限上, C++不如java和C#

对于类域范围内,成员函数访问无所谓访问限制。对于继承情况下的基类private成员,在派生类中仍然继承了下来,只不过它不能直接访问,即使在类里也不行,更不用说是类对象了。

补充一下,getter/setter是不应该声明为static的。那样对用户不友好。

protected数据成员本身就是一个不好的习惯(在我编写的《C++编程规范》里是明令禁止的),会导致基类与派生类的高度耦合。推荐的做法是private数据成员,再protected/public给出getter/setter(protected还是public取决于接口抽象)

举个例子:基类几个数据成员之间是要保证一致的(如String类的const char* _str和size_t _len)如果声明为protected,则编写派生类的程序员很可能改了_str而没改_len,或者给_str传了个非法值如NULL等。

proected方法成员有个很重要的用处是作callback。比如说你写个Parser,则你可以把分析到某个terminal的on_xxx()方法声明为protected virtual method。这些方法显然不适宜声明为poublic。

Post a comment

非这个主题相关的留言请到:留言本