C++对象模型
话题从下面这段C++程序说起,你认为它可以顺利执行吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class
A {
public :
void
Hello( const
std::string& name) {
std::cout <<
"hello " << name;
}
};
int
main( int argc,
char ** argv)
{
A* pa = NULL;
pa->Hello( "world" );
return
0;
}
|
试试的确可以顺利运行输出hello world,奇怪吗?其实并不奇怪,根据C++对象模型,类的非虚方法并不会存在于对象内存布局中,实际上编译器是把Hello方法转化成了类似这样的全局函数:
1
2
3
|
void
A_Hello_xxx(A * const
this , const
std::string& name) {
std::cout << “hello “ << name;
}
|
对象指针其实是作为第一个参数被隐式传递的,pa->Hello(“world”)实际上是调用的A_Hello_xxx(pa, “world”),而恰好A_Hello_xxx内部没有使用pa,所以这段代码得以顺利运行。
对象的消息模型
如果是研究C++对象模型,上面的讨论可以到此为止,不过这里我想从另一个层面来继续探讨这个问题。OOP的先驱人物Alan Kay在总结Smalltalk的OO特征时强调:
Smalltalk is not only NOT its syntax or the classlibrary, it is not even about classes. I’m sorry that I long ago coinedthe term “objects” for this topic because it gets many people to focuson the lesser idea. The big idea is “messaging”.
也就是说相比类和对象的概念来讲,他认为对象交互的消息模型是OOP更为本质的特征,因为消息关注的是对象间的接口和交互,在构建大的系统的时候重要的不是对象/模块的内部状态,而是它们的交互。根据消息模型,牛.吃(草)的语义是发送一条消息给“牛”,消息的类型是“吃”,消息的内容是“草”。如果按照严格的消息模型,那么上面那段C++代码应解释为向一个NULL对象发送Hello消息,这显然是不应该顺利执行的。类似的代码如果是在Java或C#中则会抛出空引用异常,所以Java和C#的设计更符合消息模型。
不过,Java和C#中也并非完全符合消息模型,来看一个经典的封装问题:
1
2
3
4
5
6
7
8
9
10
11
|
public
class Account {
private
int _amount;
public
void Transfer(Account acc,
int delta) {
acc._amount += delta;
this ._amount -= delta;
}
…
}
|
上面定义了一个Account类,问题在于为什么在这个类的Transfer方法中可以直接访问另一个对象acc的私有成员_amount呢?这是不是有破坏封装的嫌疑呢?这个问题经典的答案是:并不破坏封装,封装是划分了基于类的静态的代码边界,使得类的private代码修改不影响外界,而不是对于动态对象的保护。这个解释当然是合理的,不过正如上面C++代码的解释属于C++对象模型范畴,这个解释则属于基于类的静态类型OOP语言的范畴。消息模型强调了对象内部状态的保护,只能通过消息改变其状态,而对象内部是否真的具有_amout这样一个私有成员对其他任何对象(即使同类对象)都是未知的。
如果要严格遵守消息模型实现对象内部状态的保护应该怎么做呢?我们来看一个例子,定义一个集合类,包括:1.集合对象的构造函数;2.In方法:判断元素是否存在;3.Join方法:对两个集合做交集;4.Union方法:对两个集合做并集。下面是一种Javascript实现:
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
|
function
Set() {
var
_elements = arguments;
this .In =
function (e) {
for
( var
i = 0; i < _elements.length; ++i) {
if
(_elements[i] == e) return
true ;
}
return
false ;
};
}
Set.prototype.Join =
function (s2) {
var
s1 = this ;
var
s = new
Set();
s.In =
function (e) {
return s1.In(e) && s2.In(e); }
return
s;
};
Set.prototype.Union =
function (s2) {
var
s1 = this ;
var
s = new
Set();
s.In =
function (e) {
return s1.In(e) || s2.In(e); }
return
s;
};
var
s1 = new
Set(1, 2, 3, 4, 5);
var
s2 = new
Set(2, 3, 4, 5, 6);
var
s3 = new
Set(3, 4, 5, 6, 7);
assert( false
== s1.Join(s2).Join(s3).In(2));
assert( true
== s1.Join(s2).Uion(s3).In(7));
|
如果是在静态类型OOP语言中,要实现集合类的Join或Union,我们多半会像上面Account的例子一样直接对s2内部的_elements进行操作,而上面这段Javascript定义的Set关于对象s2的访问完全是符合消息模型的基于接口的访问。要实现消息模型Javascript的prototype机制并非必须的,真正的关键在于函数式的高级函数和闭包特性。从这个例子我们也可以体会到函数式的优点不仅在于无副作用,函数的可组合性也是函数式编程强大的原因。
Method Missing
接下来我们还要进行深度历险,让我们思考一下如果发送一条对象不能识别的消息会怎样?这种情况在C++、Java、C#等静态类型语言中会得到一个方法未定义的编译错误,如果是在Javascript中则会产生运行时异常。比如,s1.count()会产生一个运行时异常:Object#<Set> has no method ‘count’。
在静态类型语言这个问题很少受到重视,但在动态类型语言中却大有文章,来看下面的例子:
//Ruby
1
2
3
4
5
6
7
8
9
10
11
|
builder = Builder::XmlMarkup. new
xml = builder.books {|b|
b.book
:isbn =>
"14134" do
b.title
"Revelation Space"
b.author
"Alastair Reynolds"
end
b.book
:isbn =>
"53534" do
b.title
"Accelerando"
b.author
"Charles Stross"
end
}
|
上面这段很DSL的Ruby代码创建了这样一个XML文件对象:
1
2
3
4
5
6
7
8
9
10
|
< books >
< book
isbn = "14134" >
< title >Revelation Space</ title >
< author >Alastair Reynolds</ author >
</ book >
< book
isbn = "53534" >
< title >Accelerando</ title >
< author >Charles Stross</ author >
</ book >
</ books >
|
builder.books, b.book,b.title都是对象方法调用,由于XML的元素名是任意的,所以不可能事先定义这些方法,类似的代码如果是在Javascript中就是nomethod异常。那为什么上面的Ruby代码可以正确执行呢?其实只要理解了消息模型就很容易想明白,只需要定义一个通用的消息处理方法,所有未明确定义的消息都交给它来处理就行了,这就是所谓的Method Missing模式:
1
2
3
4
5
|
class
Foo
def
method_missing(method, *args, &block)
…
end
end
|
Method Missing除了对实现DSL很重要外,还可用于产生更好地调试和错误信息,把参数嵌入到方法名中等场合。目前,Ruby、Python、Groovy几种语言对Method Missing都有很好的支持,甚至在C# 4.0中也可以利用动态特性实现。
总结
本文主要介绍了对象的消息模型的特征,并比较了C++对象模型,Java、C#等基于类的静态类型语言中的对象模型与严格消息模型的差异,最后探讨了Method Missing相关话题。
参考
分享到:
相关推荐
多态性则是通过虚函数和抽象类实现的,它使得不同类型的对象可以对同一消息做出不同的响应。 5. **静态与动态绑定**:C++支持静态绑定(编译时绑定)和动态绑定(运行时绑定)。静态绑定与非虚函数相关,而动态绑定...
面向对象的数据模型还引入了“消息”或“方法”的概念,这些方法由接口和实现两部分组成。对象通过消息进行交互,而类则是具有相同属性和方法的对象的抽象。 在介绍面向对象的数据模型的基础上,本章还对比了面向...
多态则允许不同的对象对同一消息作出不同的响应,这主要通过虚函数和纯虚函数来实现。虚函数表(vtable)是C++实现多态的重要机制。 5. **静态成员与常量成员**:静态成员属于类本身而非类的任何实例,它们只有一个...
3. **消息模型对象**:使用消息模型对象来表示复杂的消息结构,如多部分消息或消息之间可能存在的关系。 4. **消息类别文件**:允许用户创建消息类别文件,将相似的消息定义组织在一起,提高复用性。 5. **数据结构*...
- Smalltalk是一种面向对象、反射式、基于消息传递的编程语言,它的出现对后来的面向对象编程产生了深远的影响。 - David Hay是软件工程领域的专家,他有关于企业模式的研究成果。 - Christopher Alexander是一位...
标题与描述均提到了“ArcEngine对象模型”,这暗示了我们正在探讨的是ArcGIS Engine中的对象模型,这是ESRI提供的一套用于开发地理信息系统(GIS)应用的组件库。ArcGIS Engine通过COM(Component Object Model)...
Java没有对消息机制提供直接支持,尽管可以使用函数模拟面向对象的消息机制,但与真正的面向对象消息机制相比,熟悉事件驱动程序的开发者仍然很难直接根据面向对象建模阶段的UML模型完成程序的编写。 3. 本文利用...
面向对象分析与设计模型 面向对象分析(Object-Oriented Analysis,OOA)是一种软件工程方法,它将软件系统分解成多个对象,每个对象都具有自己的属性和行为。OOA 的主要目标是识别系统中的对象和它们之间的关系,...
它通过时间轴来表示消息传递的先后顺序,有助于理解系统运行过程中不同对象间的协作方式。 - **活动图**(Activity Diagram):类似于流程图,用于描述系统的工作流程,包括任务、动作、条件判断等。活动图特别适合...
本文介绍了一种基于Java的面向对象消息机制的实现方法,该方法主要利用了Java中的接口、继承和多态等特性来构建。通过这种方式,可以创建一种类似于微软基础类库(Microsoft Foundation Class, MFC)中消息机制的...
多态性允许不同类的对象对同一消息作出不同的响应。虚函数表(vtable)是实现多态的关键,书中会详细介绍其工作原理。 5. **模板与泛型编程**:C++模板提供了一种强大的代码复用机制,允许创建泛型函数和泛型类。...
多态允许不同的对象对同一消息做出不同的响应,增强了软件的灵活性。虚函数在实现多态中起着关键作用。 6. **模板与泛型编程**:C++模板允许编写泛型代码,即不依赖于特定类型但能在多种类型上工作的代码。模板分为...
多态性使得不同的对象可以响应同一种消息。虚函数(virtual)是实现多态的关键,它允许通过基类指针调用派生类的重写版本。 7. **运算符重载**:C++允许用户为运算符提供自定义实现,使其能适用于特定类型。例如,...
在传统的对象模型中,对象之间通过方法调用来交互。但在分布式系统中,由于对象可能位于不同的网络节点上,直接的方法调用变得复杂且不可行。因此,基于消息中心的对象总线采用发布/订阅(Publish-Subscribe)模式,...
### ArcGIS 8.3 应用框架对象模型解析 #### 概述 在ArcGIS 8.3中,应用框架(Application Framework)的对象模型(Object Model)是构建自定义应用程序的基础。该模型提供了一系列接口和类,使开发人员能够扩展ArcGIS...
多态是C++的一大亮点,它允许不同类型的对象对同一消息作出不同的响应,通常通过虚函数和纯虚函数来实现。 此外,书中还详细讨论了C++的内存管理,包括栈内存和堆内存的区别,以及new和delete运算符的使用。内存...
点对点消息模型是Java消息服务(Java Message Service,简称JMS)中的一种核心消息传递模式,它在分布式系统中广泛用于实现异步通信。在这个模型中,消息生产者发送消息到一个特定的队列(Queue),而消息消费者从这...
- **UML(统一建模语言)** 是一种标准化的图形表示法,用于描绘对象模型和系统设计。 - **面向对象方法学** 强调通过对象的交互来构建系统,这包括消息传递和对象的继承关系。 2. **类和对象的概念**: - **类*...