<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>StephenChan&#039;s Tech Space &#187; C/C++</title>
	<atom:link href="http://blog.endlesscode.com/category/cpp/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.endlesscode.com</link>
	<description>Stay Hungry. Stay Foolish.</description>
	<lastBuildDate>Tue, 25 Oct 2011 01:15:07 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>zz C++ 虚函数表解析</title>
		<link>http://blog.endlesscode.com/2010/03/03/cpp-virtual-table/</link>
		<comments>http://blog.endlesscode.com/2010/03/03/cpp-virtual-table/#comments</comments>
		<pubDate>Wed, 03 Mar 2010 16:11:54 +0000</pubDate>
		<dc:creator>Stephen</dc:creator>
				<category><![CDATA[C/C++]]></category>

		<guid isPermaLink="false">http://blog.endlesscode.com/?p=349</guid>
		<description><![CDATA[前言 C++中的虚函数的作用主要是实现了多态的机制。关于多态，简而言之就是用父类型别的指针指向其子类的实例，然后通过父类的指针调用实际子类的成员 函数。这种技术可以让父类的指针有“多种形态”，这是一种泛型技术。所谓泛型技术，说白了就是试图使用不变的代码来实现可变的算法。比如：模板技术，RTTI技术，虚函数技术，要么是试图做到在编译时决议，要么试图做到运行时决议。 关于虚函数的使用方法，我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中，我只想从虚函数的实现机制上面为大家 一个清晰的剖析。 当然，相同的文章在网上也出现过一些了，但我总感觉这些文章不是很容易阅读，大段大段的代码，没有图片，没有详细的说明，没有比较，没有举一反三。不利于学习和阅读，所以这是我想写下这篇文章的原因。也希望大家多给我提意见。 言归正传，让我们一起进入虚函数的世界。 虚函数表 对C++ 了解的人都应该知道虚函数（Virtual Function）是通过一张虚函数表（Virtual Table）来实现的。简称为V-Table。在这个表中，主是要一个类的虚函数的地址表，这张表解决了继承、覆盖的问题，保证其容真实反应实际的函数。 这样，在有虚函数的类的实例中这个表被分配在了这个实例的内存中，所以，当我们用父类的指针来操作一个子类的时候，这张虚函数表就显得由为重要了，它就像一个地图一样，指明了实际所应该调用的函数。 这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到，编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置（这是为了保证正确取到虚函数的偏移量）。 这意味着我们通过对象实例的地址得到这张虚函数表，然后就可以遍历其中函数指针，并调用相应的函数。 听我扯了那么多，我可以感觉出来你现在可能比以前更加晕头转向了。 没关系，下面就是实际的例子，相信聪明的你一看就明白了。 假设我们有这样的一个类： class Base { public: virtual void f() { cout &#60;&#60; "Base::f" &#60;&#60; endl; } virtual void g() { cout &#60;&#60; "Base::g" &#60;&#60; &#8230; <a href="http://blog.endlesscode.com/2010/03/03/cpp-virtual-table/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<h2>前言</h2>
<p>C++中的虚函数的作用主要是实现了多态的机制。关于多态，简而言之就是用父类型别的指针指向其子类的实例，然后通过父类的指针调用实际子类的成员 函数。这种技术可以让父类的指针有“多种形态”，这是一种泛型技术。所谓泛型技术，说白了就是试图使用不变的代码来实现可变的算法。比如：模板技术，RTTI技术，虚函数技术，要么是试图做到在编译时决议，要么试图做到运行时决议。</p>
<p>关于虚函数的使用方法，我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中，我只想从虚函数的实现机制上面为大家 一个清晰的剖析。</p>
<p>当然，相同的文章在网上也出现过一些了，但我总感觉这些文章不是很容易阅读，大段大段的代码，没有图片，没有详细的说明，没有比较，没有举一反三。不利于学习和阅读，所以这是我想写下这篇文章的原因。也希望大家多给我提意见。</p>
<p>言归正传，让我们一起进入虚函数的世界。<span id="more-349"></span></p>
<h2>虚函数表</h2>
<p>对C++ 了解的人都应该知道虚函数（Virtual Function）是通过一张虚函数表（Virtual Table）来实现的。简称为V-Table。在这个表中，主是要一个类的虚函数的地址表，这张表解决了继承、覆盖的问题，保证其容真实反应实际的函数。 这样，在有虚函数的类的实例中这个表被分配在了这个实例的内存中，所以，当我们用父类的指针来操作一个子类的时候，这张虚函数表就显得由为重要了，它就像一个地图一样，指明了实际所应该调用的函数。</p>
<p>这里我们着重看一下这张虚函数表。在C++的标准规格说明书中说到，编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置（这是为了保证正确取到虚函数的偏移量）。 这意味着我们通过对象实例的地址得到这张虚函数表，然后就可以遍历其中函数指针，并调用相应的函数。</p>
<p>听我扯了那么多，我可以感觉出来你现在可能比以前更加晕头转向了。 没关系，下面就是实际的例子，相信聪明的你一看就明白了。</p>
<p>假设我们有这样的一个类：</p>
<pre class="brush:c++">class Base {
public:
virtual void f() { cout &lt;&lt; "Base::f" &lt;&lt; endl; }
virtual void g() { cout &lt;&lt; "Base::g" &lt;&lt; endl; }
virtual void h() { cout &lt;&lt; "Base::h" &lt;&lt; endl; }
};</pre>
<p>按照上面的说法，我们可以通过Base的实例来得到虚函数表。 下面是实际例程：</p>
<pre class="brush:c++">typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
cout &lt;&lt; "虚函数表地址：" &lt;&lt; (int*)(&amp;b) &lt;&lt; endl;
cout &lt;&lt; "虚函数表 — 第一个函数地址：" &lt;&lt; (int*)*(int*)(&amp;b) &lt;&lt; endl;

// Invoke the first virtual function
pFun = (Fun)*((int*)*(int*)(&amp;b));
pFun();</pre>
<p>实际运行经果如下：(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)</p>
<pre class="brush:c++">虚函数表地址：0012FED4
虚函数表 — 第一个函数地址：0044F148
Base::f</pre>
<p>通过这个示例，我们可以看到，我们可以通过强行把&amp;b转成int *，取得虚函数表的地址，然后，再次取址就可以得到第一个虚函数的地址了，也就是Base::f()，这在上面的程序中得到了验证（把int* 强制转成了函数指针）。通过这个示例，我们就可以知道如果要调用Base::g()和Base::h()，其代码如下：</p>
<pre class="brush:c++">(Fun)*((int*)*(int*)(&amp;b)+0); // Base::f()

(Fun)*((int*)*(int*)(&amp;b)+1); // Base::g()

(Fun)*((int*)*(int*)(&amp;b)+2); // Base::h()</pre>
<p>这个时候你应该懂了吧。什么？还是有点晕。也是，这样的代码看着太乱了。没问题，让我画个图解释一下。如下所示：</p>
<p><img class="aligncenter size-full wp-image-350" title="o_vtable1" src="http://blog.endlesscode.com/wp-content/uploads/2010/03/o_vtable1.jpg" alt="o_vtable1" width="331" height="129" /></p>
<p>注意：在上面这个图中，我在虚函数表的最后多加了一个结点，这是虚函数表的结束结点，就像字符串的结束符“\0”一样，其标志了虚函数表的 结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下，这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下，这个值是如果1，表示还有下一个虚函数表，如果值是0，表示是最后一个虚函数表。</p>
<p>下面，我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况，主要目的是为了给一个对比。在比较之下，我们可以更加清楚地知道其内部的具体实现。</p>
<h3>一般继承（无虚函数覆盖）</h3>
<p>下面，再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系：</p>
<p><img class="aligncenter size-full wp-image-351" title="o_Drawing3" src="http://blog.endlesscode.com/wp-content/uploads/2010/03/o_Drawing3.jpg" alt="o_Drawing3" width="78" height="194" /></p>
<p>请注意，在这个继承关系中，子类没有重载任何父类的函数。那么，在派生类的实例中，其虚函数表如下所示：</p>
<p>对于实例：Derive d; 的虚函数表如下：</p>
<p><img class="aligncenter size-full wp-image-352" title="o_vtable2" src="http://blog.endlesscode.com/wp-content/uploads/2010/03/o_vtable2.JPG" alt="o_vtable2" width="551" height="124" /></p>
<p>我们可以看到下面几点：</p>
<ol>
<li>虚函数按照其声明顺序放于表中。</li>
<li>父类的虚函数在子类的虚函数前面。</li>
</ol>
<p>我相信聪明的你一定可以参考前面的那个程序，来编写一段程序来验证。</p>
<h3>一般继承（有虚函数覆盖）</h3>
<p>覆盖父类的虚函数是很显然的事情，不然，虚函数就变得毫无意义。下面，我们来看一下，如果子类中有虚函数重载了父类的虚函数，会是一个什么样子？假设，我们有下面这样的一个继承关系。</p>
<p><img class="aligncenter size-full wp-image-353" title="o_Drawing4" src="http://blog.endlesscode.com/wp-content/uploads/2010/03/o_Drawing4.jpg" alt="o_Drawing4" width="78" height="194" /></p>
<p>为了让大家看到被继承过后的效果，在这个类的设计中，我只覆盖了父类的一个函数：f()。那么，对于派生类的实例，其虚函数表会是下面的一个样子：</p>
<p><img class="aligncenter size-full wp-image-354" title="o_vtable3" src="http://blog.endlesscode.com/wp-content/uploads/2010/03/o_vtable3.JPG" alt="o_vtable3" width="500" height="124" /></p>
<p>我们从表中可以看到下面几点，</p>
<ol>
<li>覆盖的f()函数被放到了虚表中原来父类虚函数的位置。</li>
<li>没有被覆盖的函数依旧。</li>
</ol>
<p>这样，我们就可以看到对于下面这样的程序，</p>
<pre class="brush:c++">Base *b = new Derive();
b-&gt;f();</pre>
<p>由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代，于是在实际调用发生时，是Derive::f()被调用了。这就实现了多态。</p>
<h3>多重继承（无虚函数覆盖）</h3>
<p>下面，再让我们来看看多重继承中的情况，假设有下面这样一个类的继承关系。注意：子类并没有覆盖父类的函数。</p>
<p><img class="aligncenter size-full wp-image-355" title="o_Drawing1" src="http://blog.endlesscode.com/wp-content/uploads/2010/03/o_Drawing1.jpg" alt="o_Drawing1" width="282" height="192" /></p>
<p>对于子类实例中的虚函数表，是下面这个样子：</p>
<p><img class="aligncenter size-full wp-image-356" title="o_vtable4" src="http://blog.endlesscode.com/wp-content/uploads/2010/03/o_vtable4.JPG" alt="o_vtable4" width="493" height="173" /></p>
<p>我们可以看到：</p>
<ol>
<li>每个父类都有自己的虚表。</li>
<li>子类的成员函数被放到了第一个父类的表中。（所谓的第一个父类是按照声明顺序来判断的）</li>
</ol>
<p>这样做就是为了解决不同的父类类型的指针指向同一个子类实例，而能够调用到实际的函数。</p>
<h3>多重继承（有虚函数覆盖）</h3>
<p>下面我们再来看看，如果发生虚函数覆盖的情况。</p>
<p>下图中，我们在子类中覆盖了父类的f()函数。</p>
<p><img class="aligncenter size-full wp-image-357" title="o_Drawing2" src="http://blog.endlesscode.com/wp-content/uploads/2010/03/o_Drawing2.jpg" alt="o_Drawing2" width="282" height="192" /></p>
<p>下面是对于子类实例中的虚函数表的图：</p>
<p><img class="aligncenter size-full wp-image-358" title="o_vtable5" src="http://blog.endlesscode.com/wp-content/uploads/2010/03/o_vtable5.jpg" alt="o_vtable5" width="420" height="173" /></p>
<p>我们可以看见，三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样，我们就可以任一静态类型的父类来指向子类，并调用子类的f()了。如：</p>
<pre class="brush:c++">Derive d;

Base1 *b1 = &amp;d;
Base2 *b2 = &amp;d;
Base3 *b3 = &amp;d;

b1-&gt;f(); //Derive::f()
b2-&gt;f(); //Derive::f()
b3-&gt;f(); //Derive::f()

b1-&gt;g(); //Base1::g()
b2-&gt;g(); //Base2::g()
b3-&gt;g(); //Base3::g()</pre>
<h2>安全性</h2>
<p>每次写C++的文章，总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述，相信我们对虚函数表有一个比较细致的了解了。水可载舟，亦可覆舟。下面，让我们来看看我们可以用虚函数表来干点什么坏事吧。</p>
<h3><strong>通过父类型的指针访问子类自己的虚函数</strong></h3>
<p>我们知道，子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数，但我们根本不可能使用下面的语句来调用子类的自有虚函数：</p>
<pre class="brush:c++">Base1 *b1 = new Derive();
b1-&gt;f1(); //编译出错</pre>
<p>任何妄图使用父类指针想调用子类中的<strong>未覆盖父类的成员函数</strong>的行为都会被编译器视为非法，所以，这样的程序根本无法编译通过。但在运行时，我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。（关于这方面的尝试，通过阅读后面附录的代码，相信你可以做到这一点）</p>
<h3><strong>访问non-public</strong><strong>的虚函数</strong></h3>
<p>另外，如果父类的虚函数是private或是protected的，但这些非public的虚函数同样会存在于虚函数表中，所以，我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数，这是很容易做到的。</p>
<p>如：</p>
<pre class="brush:c++">class Base {
private:
virtual void f() { cout &lt;&lt; "Base::f" &lt;&lt; endl; }
};

class Derive : public Base{
};

typedef void(*Fun)(void);

void main() {
Derive d;
Fun pFun = (Fun)*((int*)*(int*)(&amp;d)+0);
pFun();
}</pre>
<h2>结束语</h2>
<p>C++这门语言是一门Magic的语言，对于程序员来说，我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言，我们就必需要了解C++里面的那些东西，需要去了解C++中那些危险的东西。不然，这是一种搬起石头砸自己脚的编程语言。</p>
<p>在文章束之前还是介绍一下自己吧。我从事软件研发有十个年头了，目前是软件开发技术主管，技术方面，主攻Unix/C/C++，比较喜欢网络上的技术，比如分布式计算，网格计算，P2P，Ajax等一切和互联网相关的东西。管理方面比较擅长于团队建设，技术趋势分析，项目管理。欢迎大家和我 交流，我的MSN和Email是：<a href="mailto:haoel@hotmail.com">haoel@hotmail.com</a></p>
<h3>附录一：VC中查看虚函数表</h3>
<p>我们可以在VC的IDE环境中的Debug状态下展开类的实例就可以看到虚函数表了（并不是很完整的）</p>
<p><img class="aligncenter size-full wp-image-359" title="o_vtable_vc" src="http://blog.endlesscode.com/wp-content/uploads/2010/03/o_vtable_vc.JPG" alt="o_vtable_vc" width="580" height="312" /></p>
<h3>附录 二：例程</h3>
<p>下面是一个关于多重继承的虚函数表访问的例程：</p>
<pre class="brush:c++">#include &lt;iostream&gt;

using namespace std;

class Base1 {
public:
virtual void f() { cout &lt;&lt; "Base1::f" &lt;&lt; endl; }
virtual void g() { cout &lt;&lt; "Base1::g" &lt;&lt; endl; }
virtual void h() { cout &lt;&lt; "Base1::h" &lt;&lt; endl; }
};

class Base2 {
public:
virtual void f() { cout &lt;&lt; "Base2::f" &lt;&lt; endl; }
virtual void g() { cout &lt;&lt; "Base2::g" &lt;&lt; endl; }
virtual void h() { cout &lt;&lt; "Base2::h" &lt;&lt; endl; }
};

class Base3 {
public:
virtual void f() { cout &lt;&lt; "Base3::f" &lt;&lt; endl; }
virtual void g() { cout &lt;&lt; "Base3::g" &lt;&lt; endl; }
virtual void h() { cout &lt;&lt; "Base3::h" &lt;&lt; endl; }
};

class Derive : public Base1, public Base2, public Base3 {
public:
virtual void f() { cout &lt;&lt; "Derive::f" &lt;&lt; endl; }
virtual void g1() { cout &lt;&lt; "Derive::g1" &lt;&lt; endl; }
};

typedef void(*Fun)(void);

int main()
{
Fun pFun = NULL;
Derive d;

int** pVtab = (int**)&amp;d;

//Base1's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&amp;d+0)+0);
pFun = (Fun)pVtab[0][0];
pFun();

//pFun = (Fun)*((int*)*(int*)((int*)&amp;d+0)+1);
pFun = (Fun)pVtab[0][1];
pFun();

//pFun = (Fun)*((int*)*(int*)((int*)&amp;d+0)+2);
pFun = (Fun)pVtab[0][2];
pFun();

//Derive's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&amp;d+0)+3);
pFun = (Fun)pVtab[0][3];
pFun();

//The tail of the vtable
pFun = (Fun)pVtab[0][4];
cout&lt;&lt;pFun&lt;&lt;endl;

//Base2's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&amp;d+1)+0);
pFun = (Fun)pVtab[1][0];
pFun();

//pFun = (Fun)*((int*)*(int*)((int*)&amp;d+1)+1);
pFun = (Fun)pVtab[1][1];
pFun();

pFun = (Fun)pVtab[1][2];
pFun();

//The tail of the vtable
pFun = (Fun)pVtab[1][3];
cout&lt;&lt;pFun&lt;&lt;endl;

//Base3's vtable
//pFun = (Fun)*((int*)*(int*)((int*)&amp;d+1)+0);
pFun = (Fun)pVtab[2][0];
pFun();

//pFun = (Fun)*((int*)*(int*)((int*)&amp;d+1)+1);
pFun = (Fun)pVtab[2][1];
pFun();

pFun = (Fun)pVtab[2][2];
pFun();

//The tail of the vtable
pFun = (Fun)pVtab[2][3];
cout&lt;&lt;pFun&lt;&lt;endl;

return 0;
}</pre>
<h3>来源：陈皓 <a href="http://blog.csdn.net/haoel">http://blog.csdn.net/haoel</a></h3>
]]></content:encoded>
			<wfw:commentRss>http://blog.endlesscode.com/2010/03/03/cpp-virtual-table/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>读《C++对象模型》- 执行期语意学</title>
		<link>http://blog.endlesscode.com/2010/03/01/runtime-semantics/</link>
		<comments>http://blog.endlesscode.com/2010/03/01/runtime-semantics/#comments</comments>
		<pubDate>Mon, 01 Mar 2010 17:06:26 +0000</pubDate>
		<dc:creator>Stephen</dc:creator>
				<category><![CDATA[C/C++]]></category>

		<guid isPermaLink="false">http://blog.endlesscode.com/?p=331</guid>
		<description><![CDATA[一、对象的构造和解构 一般而言，constructor和destructor的安插都如下： //C++伪码 { Point point; // point.Point::Point() //一般会被安插在这里 ... // point.Point::~Point() //一般会被安插在这里 } 如果一个作用域或函数中有一个以上的离开点，情况会稍微混乱一些。Destructor必须被放在每一个离开点之前，例如： { Point point; // constructor 在这里 switch( int (point.x()) ) { case -1: //... // destructor在这里 return; case 0: //... //destructor在这里 return; case 1: //... &#8230; <a href="http://blog.endlesscode.com/2010/03/01/runtime-semantics/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<h2>一、对象的构造和解构</h2>
<p>一般而言，constructor和destructor的安插都如下：</p>
<pre class="brush:c++">//C++伪码
{
    Point point;
    // point.Point::Point()   //一般会被安插在这里
    ...
    // point.Point::~Point()   //一般会被安插在这里
}</pre>
<p>如果一个作用域或函数中有一个以上的离开点，情况会稍微混乱一些。Destructor必须被放在每一个离开点之前，例如：</p>
<pre class="brush:c++">{
    Point point;
    // constructor 在这里
    switch( int (point.x()) ) {
        case -1:
            //...
            // destructor在这里
            return;
        case 0:
            //...
            //destructor在这里
            return;
        case 1:
            //...
            //destructor在这里
            return;
        default:
            //...
            //destructor在这里
            return;
    }
    //destructor在这里
}</pre>
<p><span id="more-331"></span><br />
在这个例子中，point的destructor必须在switch指令四个出口的return操作前被生成出来。另外也很有可能在这个作用域的结束符号之前被生成出来。因此，一般而言，我们会把object尽可能放置在使用它的那个程序区段附近，这样做可以节省不必须的对象产生操作和摧毁操作。</p>
<h3>全局对象</h3>
<p>C++程序中所有的global objects都被放置在程序的data segment中。如果明确指定给它一个值，object将以该值为初值，否则object所配置到的内容为0（这和C略有不同，C并不自动设定初值）。对于全局的class object，其在编译时期可以被放置于data segment中并且内容为0，但constructor一直要到程序运行时才会执行。<br />
对于每个全局对象，编译器会为其产生一个相应的初始化函数和内存释放函数，这些函数在编译时期就通过一定的方法链接起来，并插在main函数的开始处和结束处。<br />
<img class="aligncenter size-full wp-image-333" title="sti&amp;std" src="http://blog.endlesscode.com/wp-content/uploads/2010/02/stistd.jpg" alt="sti&amp;std" width="550" height="327" /></p>
<h3>局部静态对象</h3>
<p>对于局部静态对象，可以通过创建一个临时性的标记对象，用来判断在函数调用的时候局部静态对象是否已经被初始化，而不用在程序启动的时候将所有局部静态对象都构造出来。</p>
<h2>二、对象数组</h2>
<p>对于像下面的数组定义：</p>
<pre class="brush:c++">Point knots[10];</pre>
<p>Point有一个default constructor，因此这个constructor必须轮流施行于knots每一个元素之上。在cfront中，使用一个命名为vec_new()的函数，产生出以class objects构造而成的数组。比较晚近的编译器，包括Borland、Microsoft和Sun，则是提供了两个函数，一个用来处理“没有virtual base class”的class，另一个用来处理“内带virtual base class”的class。后一个函数通常称为vec_vnew()。函数类型通常如下（当然在各平台上可能会有些许差异存在）：</p>
<pre class="brush:c++">void* vec_new(
    void *array,                                      //数组起始地址
    size_t elem_size,                                //每一个class object的大小
    int elem_count,                                  //数组中的元素数目
    void (*constructor)( void* ),
    void (*destructor)( void*, char)
}</pre>
<p>其中constructor和destructor参数是这个class的default constructor和default destructor的函数指针，虽然constructor和destructor在程序代码是没法获取到的，但是在这里有编译器的支持，因此完全可以获取到constructor和destructor的函数指针。下面是编译器可能针对上面定义的10个Point元素所做的vec_new()调用操作：</p>
<pre class="brush:c++">Point knots[10];
vec_new( &amp;knots, sizeof( Point ), 10, &amp;Point::Point, 0);</pre>
<p>如果Point也定义了一个destructor，当knots的生命结束时，该destructor也必须施行于这10个Point元素身上，这是经由一个类似于vec_delete()的runtime library函数完成的，其函数原型如下：</p>
<pre class="brush:c++">void* vec_delete(
    void *array,                                      //数组起始地址
    size_t elem_size,                                //每一个class object的大小
    int elem_count,                                  //数组中的元素数目
    void (*destructor)( void*, char)
}</pre>
<p>如果在程序员代码中提供了一个或多个明显初始给一个由class objects组成的数组，像下面这样：</p>
<pre class="brush:c++">Point knots[10] = {
    Point(),
    Point(1.0, 1,0, 0.5),
    -1.0
};
</pre>
<p>那么，对于那些明显获得初值的元素，vec_new()不再有必要。对于那些尚未被初始化的元素，vec_new()的施行方式就可能被转换为：</p>
<pre class="brush:c++">Point knots[0];
//明确地初始化前3个元素
Point::Point( &amp;knots[0] );
Point::Point( &amp;knots[1], 1.0, 1.0, 0.5 );
Point::Point( &amp;knots[2], -1.0, 0.0, 0.0 );
//以vec_new初始化后7个元素
vec_new( &amp;knots+3, sizeof( Point ), 7, &amp;Point::Point, 0);</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.endlesscode.com/2010/03/01/runtime-semantics/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>读《C++对象模型》- 构造、解构、拷贝语意学</title>
		<link>http://blog.endlesscode.com/2010/02/28/semantics-of-construction-destruction-copy/</link>
		<comments>http://blog.endlesscode.com/2010/02/28/semantics-of-construction-destruction-copy/#comments</comments>
		<pubDate>Mon, 01 Mar 2010 00:41:21 +0000</pubDate>
		<dc:creator>Stephen</dc:creator>
				<category><![CDATA[C/C++]]></category>

		<guid isPermaLink="false">http://blog.endlesscode.com/?p=326</guid>
		<description><![CDATA[constructor可能内带大量的隐藏码，因为编译器会扩充每一个constructor，扩充程度视class的继承体系而定。一般而言编译器所做的扩充操作大约如下： 记录在member initialization list中的data members初始化操作会被放进constructor的函数本身，并以members的声明顺序为顺序。 如果有一个member并没有出现在member initialization之中，但它有一个default constructor，那么该default constructor必须被调用。 在2之前，如果class object有virtual table pointer(s)，它（们）被设定初值，指向适当的virtual table(s)。 在3之前，所有上一层的base class constructors必须被调用，以base class的声明顺序为顺序： 如果base class被列于member initialization list中，那么任何明确指定的参数都应该传递过去。 如果base class没有被列于member initialization list中，而它有default constructor，那么就调用之。 如果base class是多重继承下的第二或后继的base class，那么this指针必须有所调整。 在4之前，所有virtual base class constructors必须被调用，从左到右，从最深到最浅： 如果class被列于member initialization list中，那么如果有任何明确指定的参数，都应该传递过去。若没有列于list之中，而class中一个default constructor，也应该调用之。 此外，class中的每一个virtual base &#8230; <a href="http://blog.endlesscode.com/2010/02/28/semantics-of-construction-destruction-copy/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>constructor可能内带大量的隐藏码，因为编译器会扩充每一个constructor，扩充程度视class的继承体系而定。一般而言编译器所做的扩充操作大约如下：</p>
<ol>
<li>记录在member initialization list中的data members初始化操作会被放进constructor的函数本身，并以members的声明顺序为顺序。</li>
<li>如果有一个member并没有出现在member initialization之中，但它有一个default constructor，那么该default constructor必须被调用。</li>
<li>在<strong>2</strong>之前，如果class object有virtual table pointer(s)，它（们）被设定初值，指向适当的virtual table(s)。</li>
<li>在<strong>3</strong>之前，所有上一层的base class constructors必须被调用，以base class的声明顺序为顺序：
<ul>
<li>如果base class被列于member initialization list中，那么任何明确指定的参数都应该传递过去。</li>
</ul>
<ul>
<li>如果base class没有被列于member initialization list中，而它有default constructor，那么就调用之。</li>
</ul>
<ul>
<li>如果base class是多重继承下的第二或后继的base class，那么this指针必须有所调整。</li>
</ul>
</li>
<li>在<strong>4</strong>之前，所有virtual base class constructors必须被调用，从左到右，从最深到最浅：
<ul>
<li>如果class被列于member initialization list中，那么如果有任何明确指定的参数，都应该传递过去。若没有列于list之中，而class中一个default constructor，也应该调用之。</li>
</ul>
<ul>
<li>此外，class中的每一个virtual base class subobject的偏移量(offset)必须在执行期可被存取。</li>
</ul>
<ul>
<li>如果class object是最底层的(most-derived)的class，其constructors可能被调用；某些用以支持这个行为的机制必须被放进来。</li>
</ul>
</li>
</ol>
<p><span id="more-326"></span>对于虚拟继承，可以在derived class的constructor函数中加入条件式的测试参数，用来决定调用还是不调用相关的virtual base class constructors。</p>
<p>对象定义的析构函数的执行顺序和构造的顺序相反：</p>
<ol>
<li>destructor的函数本身首先被执行。</li>
<li>如果class拥有member class object，而后者拥有destructors，那么它们会以其声明顺序的相反顺序被调用。</li>
<li>如果object内带一个vptr，则现在被重新设定，指向适当之base class的virtual table。</li>
<li>如果有任何直接的（上一层）nonvirtual base classes拥有destructor，它们会以其声明顺序的相反顺序被调用。</li>
<li>如果有任何virtual base class拥有destructor，而当前讨论的这个class是最尾端(most -derived)的class，那么它们会以原来的构造顺序的相反顺序被调用。</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://blog.endlesscode.com/2010/02/28/semantics-of-construction-destruction-copy/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>读《C++对象模型》- Function语意学</title>
		<link>http://blog.endlesscode.com/2010/02/28/semantics-of-function/</link>
		<comments>http://blog.endlesscode.com/2010/02/28/semantics-of-function/#comments</comments>
		<pubDate>Sun, 28 Feb 2010 15:56:15 +0000</pubDate>
		<dc:creator>Stephen</dc:creator>
				<category><![CDATA[C/C++]]></category>

		<guid isPermaLink="false">http://blog.endlesscode.com/?p=309</guid>
		<description><![CDATA[一、成员函数的调用方式 1.Nonstatic member functions C++的设计准则之一就是：nonstatic member function 至少必须和一般的nonmember function有相同的效率。而实际上，编译器内部会将“member函数实体”转换为对等的“nonmember函数实体”。其转化步骤大致如下： 改写函数的signature（函数名称+参数数目+参数类型）以安插一个额外的参数到member function中，用以提供一个存取管道，使class object得以调用该函数。该额外参数被称为this指针。 将每一个“对nonstatic data member”的存取操作“改为经由this指针来存取”。 对member function重新写成一个外部函数。对函数名称进行“mangling”处理，使它在程序中成为独一无二的语汇（比如将参数 类型和返回值类型也编码进来）。 2.Virtual member functions 如果normalize()是一个virtual member function，那么以下调用： ptr-&#62;normalize(); 将会被内部转化为： ( * ptr-&#62;vptr[ 1 ])( ptr ); 其中： vptr表示由编译器产生的指针，指向virtual table。它被安插在每一个“声明有（或继承自）一个或多个virtual functions”的class object中。事实上其名称也会被“mangled”，因为在一个复杂的class派生体系中，可能存在多个vptrs。 1是virtual table &#8230; <a href="http://blog.endlesscode.com/2010/02/28/semantics-of-function/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<h2>一、成员函数的调用方式</h2>
<h3>1.Nonstatic member functions</h3>
<p>C++的设计准则之一就是：nonstatic member function 至少必须和一般的nonmember function有相同的效率。而实际上，编译器内部会将“member函数实体”转换为对等的“nonmember函数实体”。其转化步骤大致如下：</p>
<ul>
<li>改写函数的signature（函数名称+参数数目+参数类型）以安插一个额外的参数到member function中，用以提供一个存取管道，使class object得以调用该函数。该额外参数被称为this指针。</li>
<li>将每一个“对nonstatic data member”的存取操作“改为经由this指针来存取”。</li>
<li>对member function重新写成一个外部函数。对函数名称进行“mangling”处理，使它在程序中成为独一无二的语汇（比如将参数 类型和返回值类型也编码进来）。<span id="more-309"></span></li>
</ul>
<h3>2.Virtual member functions</h3>
<p>如果normalize()是一个virtual member function，那么以下调用：</p>
<pre class="brush:c++">ptr-&gt;normalize();</pre>
<p>将会被内部转化为：</p>
<pre class="brush:c++">( * ptr-&gt;vptr[ 1 ])( ptr );</pre>
<p>其中：</p>
<ul>
<li>vptr表示由编译器产生的指针，指向virtual table。它被安插在每一个“声明有（或继承自）一个或多个virtual functions”的class object中。事实上其名称也会被“mangled”，因为在一个复杂的class派生体系中，可能存在多个vptrs。</li>
<li>1是virtual table slot的索引值，关联到normalize()函数。</li>
<li>第二个ptr表示this指针。</li>
</ul>
<p>“经由一个class object调用一个virtual function”，这种操作应该总是被编译器像对待一般的nonstatic member function一样加以决议。如果在normalize()函数中再调用一个ptr的virtual member function，则这个function是以“class object调用”的方式进行决议的，即压制了由于虚拟机制而产生的不必要的重复调用操作。</p>
<h3>3.Static member functions</h3>
<p>静态成员函数有几个明显的特性是：</p>
<ul>
<li>没有this指针。</li>
<li>不能够直接存取其class中的nonstatic members。</li>
<li>不能够被声明为 const、volatile或virtual。</li>
<li>不需要经由 class object 才被调用 ——虽然大部分时候它是这样被调用的。</li>
</ul>
<p>一个static member function，理所当然会被提出class声明之外，并给予一个经过“mangled”的适当名称。由于static member function没有this指针，所以其地址的类型并不是一个“指向class member function的指针”，而是一个“nonmember 函数指针”。也就是说：</p>
<p>对于</p>
<pre class="brush:c++">unsigned int Point3d::object_count()
{
//....
}
&amp;Point3d::object_count();</pre>
<p>会得到一个数值，类型是：</p>
<pre class="brush:c++">unsigned int (*) ();</pre>
<p>而不是：</p>
<pre class="brush:c++">unsigned int ( Point3d::*) ();</pre>
<p>由于static member function缺乏this指针，因此差不多等同于nonmember function。</p>
<h2>二、关于Virtual member functions</h2>
<p>为了支持virtual function机制，必须首先能够对于多态对象有某种形式的“执行期类型判断法”。也就是说，以下的调用操作将需要ptr在执行期的某些相关信息</p>
<p>ptr-&gt;z();</p>
<p>如此一来才能够找到并调用z()的适当实体。</p>
<p>在C++中， virtual functions（可经同其class object被调用）可以在编译时期获知，此外，这一组地址是固定不变的，执行期不可能新增或替换之。由于程序执行时，表格的大小和内容都不会改变，所以其建构和存取皆可以由编译器完全掌握，不需要执行期的任何介入。</p>
<p>一个class只会有一个virtual table。每一个table内含其对应的 class object中所有active virtual functions函数实体的地址。这些active virtual functions包括：</p>
<ul>
<li>这个class所定义的函数实体，它会override一个可能存在的base class virtual function函数实体。</li>
<li>继承自base class的函数实体，这是在derived class不override base class virtual function时才会出现的情况。</li>
<li>一个pure_virtual_called()函数实体，它既可以扮演pure virtual function的空间保卫者角色，也可以当做执行期异常处理函数（有时候会用到）。</li>
</ul>
<p>对于</p>
<p>ptr-&gt;z();</p>
<p>如何有足够的知道在编译时期设定virtual function的调用呢？</p>
<ul>
<li>从指针是无法知道指针指向对象的类型的，但是可以通过ptr可以获取到对象的virtual table，从而可以通过virtual table的第一个slot（索引0）获得对象的类型信息。</li>
<li>即使不知道哪个z()函数实体会被调用，但是每一个z()函数地址都被放在slot4，这个是在编译时期就已经确定的了。</li>
</ul>
<p>对于多重继承下的Virtual Functions，只需要注意其有n-1个vptr指针，n表示其上一层base classes的数目（因此，单一继承将不会有额外的virtual tables）。在基类和父类的函数调用上，有时候需要编译器介入进行必要的this指针调整，以指向到正确的base class的vptr上。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.endlesscode.com/2010/02/28/semantics-of-function/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>读《C++对象模型》- Data语意学</title>
		<link>http://blog.endlesscode.com/2010/02/22/semantics-of-data/</link>
		<comments>http://blog.endlesscode.com/2010/02/22/semantics-of-data/#comments</comments>
		<pubDate>Mon, 22 Feb 2010 18:22:13 +0000</pubDate>
		<dc:creator>Stephen</dc:creator>
				<category><![CDATA[C/C++]]></category>

		<guid isPermaLink="false">http://blog.endlesscode.com/?p=262</guid>
		<description><![CDATA[一、Data Member的存取 static data members，会被编译器提出于class之外，并被视为一个global变量（但只在class生命范围之内可见）。每一个member的存取许可（private, protected, public），以及与class的关联，并不会导致任何空间上或执行时间上的额外负担——不论是在个别的class objects或是在static data member本身。每一个static data member只有一个实体，存放在程序的data segment之中。 nonstatic data members直接存放在每一个class object之中。每一个nonstatic data member的偏移量（offset）在编译时期即可获知，甚至如果member属于一个base class subobject（派生自单一或多重继承串链）也是一样。因此，存取一个nonstatic data member，其效率和存取一个C struct member或一个nonderived class的member是一样的。 但是使用继承和指针还是会带一些差异的，如下代码： // Point3d是一个derived class 其定义就忽略哈 Point3d origin, *pt = &#38;origin; origin.x = 0.0; pt-&#62;x &#8230; <a href="http://blog.endlesscode.com/2010/02/22/semantics-of-data/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<h2>一、Data Member的存取</h2>
<p>static data members，会被编译器提出于class之外，并被视为一个global变量（但只在class生命范围之内可见）。每一个member的存取许可（private, protected, public），以及与class的关联，并不会导致任何空间上或执行时间上的额外负担——不论是在个别的class objects或是在static data member本身。每一个static data member只有一个实体，存放在程序的data segment之中。</p>
<p>nonstatic data members直接存放在每一个class object之中。每一个nonstatic data member的偏移量（offset）在编译时期即可获知，甚至如果member属于一个base class subobject（派生自单一或多重继承串链）也是一样。因此，存取一个nonstatic data member，其效率和存取一个C struct member或一个nonderived class的member是一样的。</p>
<p>但是使用继承和指针还是会带一些差异的，如下代码：</p>
<pre class="brush:c++">// Point3d是一个derived class 其定义就忽略哈
Point3d origin, *pt = &amp;origin;
origin.x = 0.0;
pt-&gt;x = 0.0</pre>
<p>从上面代码中，“从origin存取”和“从pt存取”有什么重大的差异？答案是“当Point3d是一个derived class，而在其继承结构中有一个virtual base class，并且被存取的member（如上例中的x）是一个从该virtual base class继承而来的member时，就会有重大的差异“。这时候<strong>不能够说pt必然指向哪一种class type</strong>（因此也就不知道编译时期这个member真正的offset位置），所以这个存取操作必然延迟至执行期，经由一个额外的间接导引，才能够解决。但如果使用origin，就不会有这个问题，其类型无疑是Point3d Class，而即使它继承自virtual base class，members的offset位置也在编译时期就固定了。<span id="more-262"></span></p>
<h2>二、”继承“与Data Member</h2>
<p>在继承体系中，关于derived class members和base class(es) members的内存布局中的排列次序并未在C++ Standard中强制指定；理论上编译器可以自由安排之。在大部分编译器上头，base class members总是先出现，但属于virtual base class除外（一般而言，任何一条规则一旦遇上virtual base class基本就难成立的了）。</p>
<p>下面将分别讨论“单一继承且不含virtual function”、“单一继承并含virtual functions”、“多重继承”、“虚拟继承”等四种情况的继承模型。</p>
<h3>1.单一继承且不含多态</h3>
<p><img class="size-full wp-image-272 aligncenter" title="singleinherit" src="http://blog.endlesscode.com/wp-content/uploads/2010/02/singleinherit.jpg" alt="singleinherit" width="464" height="236" /></p>
<p style="text-align: center;"><strong>单一继承而且没有多态时的数据布局</strong></p>
<p>这里有一个易犯了的错误，把一个class分解为两层或更多层，有可能会为了“表现class体系之抽象化”而膨胀所需的空间。例子如下：</p>
<pre class="brush:c++">class Concrete {
public:
    //...
private:
    int val;
    char c1;
    char c2;
    char c3;
};</pre>
<p>在一部32位机器中，每一个Concrete class object的大小都是8 bytes，细分为(1)val占用4 bytes；(2)c1,c2,c3各占用1 bytes；(3)alignment需要1 byte。如下图所示：</p>
<p style="text-align: center;"><img class="alignnone size-full wp-image-274" title="concreteobject" src="http://blog.endlesscode.com/wp-content/uploads/2010/02/concreteobject.jpg" alt="concreteobject" width="206" height="206" /></p>
<p>假如将上述的Concrete分裂为三层的继承结构表示的话：</p>
<pre class="brush:c++">class Concrete1 {
public:
    //...
private:
    int val;
    char c1;
};

class Concrete2 : public Concrete1 {
public:
    //...
private:
    char c2;
};

class Concrete3 : public Concrete2 {
public:
    //...
private:
    char c3;
};</pre>
<p>经过这样分裂的设计后，Concrete3 object的大小不再是8 bytes了，而是16 bytes了。其继承结构的内存布局如下：</p>
<p style="text-align: center;"><img class="size-full wp-image-273 aligncenter" title="concrete123object" src="http://blog.endlesscode.com/wp-content/uploads/2010/02/concrete123object.jpg" alt="concrete123object" width="535" height="304" /></p>
<p>之所以出现这种情况是因为C++语言保证&#8221;出现在derived class中的base class subobject有其完整性原样性&#8221;,正是如此,继承后的Concrete3 object会因为继承串链中的父类alignment的空间而导致本身的空间膨胀.</p>
<h3>2.加上多态</h3>
<p>加上多态后，不可避免地在数据结构中多了一个vptr，C++ Standard没有规定vptr放在对象结构的位置。把vptr放在class object的尾端，可以保留base class C struct的对象布局，因而允许在C程序代码中也能使用。把vptr放在前端，对于“在多重继承之下，通过指向class members的指针调用virtual function”，会带来一些帮助，而代价就是丧失了C语言兼容性。</p>
<h3><img class="aligncenter size-full wp-image-304" title="inherit&amp;virtual" src="http://blog.endlesscode.com/wp-content/uploads/2010/02/inheritvirtual.jpg" alt="inherit&amp;virtual" width="484" height="283" />3.多重继承</h3>
<p>相对于加入多态的单一继承，多重继承在把一个derived object转换为其base类型时，需要由编译器的介入，用以调整地址。具体来说，对于一个多重继承对象，将其地址指定给“最左端（也就是第一个）base class的指针”，情况将和单一继承时相同，因为二者都指向相同的起始地址。需付出的成本只有地址的指定操作而已。至于指定给第二个或后继的base class的地址指定操作，则需要将地址修改过：加上（或减去，如果downcast的话）介于中间的base class subobject(s)大小。</p>
<h3>4.虚拟继承</h3>
<p>多重继承的一个语意上的副作用就是，它必须支持某种形式的&#8221;shared subobject&#8221;继承。一个典型的例子是最早的iostream library:</p>
<pre class="brush:c++">class ios { ... };
class istream : public ios { ... };
class ostream : public ios { ... };
class iostream : public istream, public ostream { ... };</pre>
<p>这样在iostream中得维护两份ios的subobject，而解决这个问题是使用虚拟继承：</p>
<pre class="brush:c++">class ios { ... };
class istream : public virtual ios { ... };
class ostream : public virtual ios { ... };
class iostream : public istream, public ostream { ... };</pre>
<p>至于虚拟继承的一般实现方法如下所述。Class如果内含一个或多个virtual base class subobjects，像istream那样，将被分割成两部分：一个不变局部和一个共享局部。不变局部中的数据（实际上是在本类而非在base class中定义的data members），不管后继如何衍化，总是拥有固定的offset（从object的开头算起），所以这一部分数据可以被直接存取。至于共享局部，所表现的就是virtual base class subobject。这一部分的数据，其位置会因为每次的派生操作而会有所变化，所以它们只可以被间接存储（如在istream中的ios）。一般的布局策略是先安排好derived class的不变部分，然后再建立其共享部分。<br />
有两个实现模型，第一个是在每个类中添加一个指针，用于指向共享局部的地址，即使共享局部在后继的继承中出现布局变化，但指针仍然会在编译阶段设定为正确的地址。另一个模型则是通过在virtual table上放置offset（相对于对象开头)来获取共享局部的地址。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.endlesscode.com/2010/02/22/semantics-of-data/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>读《C++对象模型》- 构造函数语意学</title>
		<link>http://blog.endlesscode.com/2010/02/21/semantics-of-constructors/</link>
		<comments>http://blog.endlesscode.com/2010/02/21/semantics-of-constructors/#comments</comments>
		<pubDate>Sun, 21 Feb 2010 17:37:46 +0000</pubDate>
		<dc:creator>Stephen</dc:creator>
				<category><![CDATA[C/C++]]></category>

		<guid isPermaLink="false">http://blog.endlesscode.com/?p=240</guid>
		<description><![CDATA[一、Default Constructor C++新手一般有两个误解： 任何class如果没有定义default constructor，就会被合成出一个来。 编译器合成出来的default constructor会明确设定&#8221;class内每一个data member的默认值&#8221;。 有四种情况，会导致“编译器必须为未声明constructor之classes合成一个default constructor“。C++ Standard把那些合成物称为implicit nontrivial default constructors（隐式并有价值的默认构造函数）。至于没有存在那四种情况而又没有声明任何constructor的classess，则说它们拥有的是implicit trivial default constructors，它们实际上并不会被合成出来。那么这四种有价值的默认构造函数分别是： 带有Default Constructor的Member Class Object。 带有Default Constructor的Base Class。 带有一个Virtual Function的Class。 带有一个Virtual Base Class的Class（Virtual Base Class是用来被虚拟继承的基类，防止在一个对象中重复出现几个基类）。 对于&#8221;带有Default Constructor的Member Class Object&#8221;，编译器会扩张已存在的construtors，没有则会合成只满足编译器需要的default constructor。然后在constructors中安插一些代码，使得user code在被执行之前，先调用必要的member class objects&#8217; construtors，有多个member &#8230; <a href="http://blog.endlesscode.com/2010/02/21/semantics-of-constructors/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<h2>一、Default Constructor</h2>
<p>C++新手一般有两个误解：</p>
<ol>
<li>任何class如果没有定义default constructor，就会被合成出一个来。</li>
<li>编译器合成出来的default constructor会明确设定&#8221;class内每一个data member的默认值&#8221;。</li>
</ol>
<p>有四种情况，会导致“编译器必须为未声明constructor之classes合成一个default constructor“。C++ Standard把那些合成物称为implicit nontrivial default constructors（隐式并有价值的默认构造函数）。至于没有存在那四种情况而又没有声明任何constructor的classess，则说它们拥有的是implicit trivial default constructors，它们实际上并不会被合成出来。那么这四种有价值的默认构造函数分别是：</p>
<ul>
<li>带有Default Constructor的Member Class Object。</li>
<li>带有Default Constructor的Base Class。</li>
<li>带有一个Virtual Function的Class。</li>
<li>带有一个Virtual Base Class的Class（Virtual Base Class是用来被虚拟继承的基类，防止在一个对象中重复出现几个基类）。</li>
</ul>
<p>对于&#8221;带有Default Constructor的Member Class Object&#8221;，编译器会扩张已存在的construtors，没有则会合成只满足编译器需要的default constructor。然后在constructors中安插一些代码，使得user code在被执行之前，先调用必要的member class objects&#8217; construtors，有多个member class objects则按照在class中声明的次序进行初始化。</p>
<p>对于&#8221;带有Default Constructor的Base Class&#8221;，含有多个时仍会按照声明的次序进行base class的初始化，如果同时存在着&#8221;带有Default Constructor的Member Class Object&#8221;，则member class objects的default constructor也会在base class constructor都<strong>被调用之后</strong>再调用。<span id="more-240"></span></p>
<p><strong><span style="color: #ff0000;">对于&#8221;带有一个Virtual Function或Virtual Base Class的Class如何初始化vptr&#8221;以后再补充</span></strong></p>
<p>在合成的default constructor中，只有base class subojects和member class objects会被初始化。所有其它的nonstatic data member，如整数、整数指针、整数数组等等都不会被初始化。</p>
<h2>二、Copy Constructor</h2>
<p>有三种情况，会以一个object的内容作为另一个class object的初值。</p>
<ul>
<li>赋值的方式创建对象。如 X x; X xx = x;</li>
<li>值参数传递。如 X xx; foo( xx );</li>
<li>函数返回值。如 X foo() { X xx; &#8230;; return xx; }</li>
</ul>
<p>如果class没有提供一个explicit copy constructor又当如何？当class object以“相同class的另一个object“作为初值时，其内部是以所谓的default memberwise initialization手法完成的，也就是把每一个内建或派生的data member的值，从某个object拷贝一份到另一个object身上。不过它并不会拷贝其中的member class object，而是以递归的方式施行memberwise initialization。</p>
<p>和以前一样，C++ Standard把copy constructor区分为trivial和nontrivial两种。只有nontrivial的实体才会被合成于程序之中，并执行default memeberwise initialization。决定一个copy constructor是否为trivial的标准在于class是否展现出所谓的&#8221;bitwise copy semantics&#8221;，顾名思义，若展现出bitwise copy semantics则不合成default copy constructor，不执行default memberwise initialization，而执行以bit的方式拷贝对象成员数据。</p>
<p>什么时候一个class不展现出&#8221;bitwise copy semantics&#8221;呢？其实这个与default constructor的4种情况基本是一样的：</p>
<ul>
<li>当class内含一个member object而后者的class声明有一个copy constructor时（不论是被class设计者明确地声明的，或是被编译器合成的）。</li>
<li>当class继承自一个base class而后者存在有一个copy constructor时（再次强调，不论是被明确声明还是被合成而得的）。</li>
<li>当class声明了一个或多个virtual functions时。</li>
<li>当class派生自一个继承串链，其中有一个或多个virtual base classes时。</li>
</ul>
<p>关于其中的Virtual Table指针的设定，在编译期间有两个程序扩张操作（只要有一个class声明了一个或多个virtual functions就会如此）：</p>
<ul>
<li>增加一个virtual function table(vtbl)，内含每一个有作用的virtual function的地址。</li>
<li>将一个指向virtual function table的指针(vptr)，安插在每一个class object内。</li>
</ul>
<p>当编译器导入一个vptr到class之中时，该class就不再展现bitwise semantics了。现在，编译器需要合成出一个copy constructor，以求将vptr适当地初始化，例子如下 ：</p>
<pre class="brush:c++">class ZooAnimal {
public:
    ZooAnimal();
    virtual ~ZooAnimal();

    virtual void animate();
    virtual void draw();
    //...
private:
    // ZooAnimal的animate()和draw()
    // 所需要的数据
};

class Bear : public ZooAnimal {
public:
    Bear();
    void animate();
    void draw();
    virtual void dance();
    //...
private:
    // Bear的animate()和draw()和dance()
    // 所需要的数据
};</pre>
<p>上面的代码中Bear派生于ZooAnimal，并且带有virtual functions，也就不再展现bitwise semantics了。下面的初始化与其对应的类结构：</p>
<pre class="brush:c++">Bear yogi;
Bear winnie = yogi;</pre>
<p style="text-align: center;"><img class="size-full wp-image-248 aligncenter" title="yogi&amp;winnie" src="http://blog.endlesscode.com/wp-content/uploads/2010/02/yogiwinnie.jpg" alt="yogi&amp;winnie" width="635" height="286" /></p>
<p style="text-align: center;"><strong>yogi和winnie的关系</strong></p>
<p>当一个base class object以其derived class的object内容做初始化操作时，其vptr复制操作也必须保证安全：</p>
<pre class="brush:c++">Bear yogi;
ZooAnimal franny = yogi;     //注意，这里会发生切割行为
franny.draw();  //这里调用的是ZooAnimal的draw成员函数，如果是ZooAnimal &amp;franny = yogi，则调用Bear::draw();</pre>
<p style="text-align: center;"><img class="size-full wp-image-249 aligncenter" title="yogi&amp;franny" src="http://blog.endlesscode.com/wp-content/uploads/2010/02/yogifranny.jpg" alt="yogi&amp;franny" width="667" height="430" /></p>
<p style="text-align: center;"><strong>franny和yogi的关系</strong></p>
<p>合成出来的ZooAnimal copy constructor会明确设定object的vptr指向ZooAnimal class的virtual table，而不是直接从右手边的class object中将其现值拷贝过来。<br />
copy constructor的应用，迫使编译器多多少少对你的程序代码做部分转化。尤其是当一个函数以传值（by value）的方式传回一个class object，而该class object有一个copy constructor（不论是明确定义出来的，或是合成的）时。这将导致深奥的程序转化——不论在函数的定义或应用上。举个例子：</p>
<pre class="brush:c++">X bar()
{
    X xx;
    // ...处理 xx
    return xx;
}</pre>
<p>编译器会把其中的xx以__result取代：</p>
<pre class="brush:c++">void bar( X &amp;__result )
{
    // default constructor被调用
    // C++伪码
    __result.X::X();
    // ...处理xx
    return; //注意是直接return
}</pre>
<p>这样的编译器优化操作，有时候被称为Named Return Value(NRV)优化，NRV优化如何被视为是标准C++编译器的一个义不容辞的优化操作。对于这样的NRV优化操作，需要一个explicit copy constructor定义来激活。但是并不是所有的代码都可以进行这样的优化，复杂的函数会使NRV优化难以实施。</p>
<h2>三、Member Initialization List</h2>
<p>对于没有使用initialization list的情况：</p>
<pre class="brush:c++">class Word {
    String _name;
    int _cnt;
public:
    Word() {
        _name = 0;
        _cnt = 0;
    }
};</pre>
<p>上面的代码是没有错误的，但是实际上，Word Constructor会先产生一个暂时性的String object，然后将它初始化，再以一个assignment运算符将暂时性object指定给_name，然后再摧毁那个暂时性object，下面是编译器可能的内部扩张结果：</p>
<pre class="brush:c++">// C++伪码
Word::Word( /* this pointer goes here */ )
{
    // 调用String的default constructor
    _name.String::String()

    // 产生暂时性对象
    String temp = String(0);

    // "memberwise"地拷贝 _name
    _name.String::operator=( temp );

    // 摧毁暂时性对象
    temp.String::~String();

    _cnt = 0;
}</pre>
<p>当然，更有效率的初始化方法是这样的：</p>
<pre class="brush:c++">Word::Word : _name(0)
{
    _cnt = 0;
}</pre>
<p>这样使用initialization list初始化大致会被扩展成下面这样：</p>
<pre class="brush:c++">// C++伪码
Word::Word( /* this pionter goes here */ )
{
    // 调用String( int ) constructor
    _name.String::String( 0 );
    _cnt = 0;
}</pre>
<p>对于initialization list的语法，其实际上并不是一组函数调用，而是对list对象的copy constructor的调用或者是直接赋值(如int)。编译器会一一操作initialization list，以适当次序在constructor之内安插初始化操作，并且在任何explicit user code之前，而list中的项目次序是由class中members声明次序所决定的，不是由initialization list中排列次序决定的。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.endlesscode.com/2010/02/21/semantics-of-constructors/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>读《C++对象模型》- 对象模型</title>
		<link>http://blog.endlesscode.com/2010/02/21/about-object/</link>
		<comments>http://blog.endlesscode.com/2010/02/21/about-object/#comments</comments>
		<pubDate>Sun, 21 Feb 2010 17:35:24 +0000</pubDate>
		<dc:creator>Stephen</dc:creator>
				<category><![CDATA[C/C++]]></category>

		<guid isPermaLink="false">http://blog.endlesscode.com/?p=234</guid>
		<description><![CDATA[在C++的对象模型中，nonstatic data members被配置于每一个class object之内，static data members则被存放在所有的class object之外，而static和nonstatic function members也被放在所有的class object之外。Virtual functions则以两个步骤支持之： 每一个class产生出一堆指向virtual functions的指针，放在表格之中。这个表格称为virtual table(vtbl)。 每一个class object被添加了一个指针，指向相关的virtual table。通常这个指针被称为vptr。vptr的设定和重置都由每一个class的constructor、destructor和copy assignment运算符自动完成。每一个class所关联的type_info object（用以支持runtime type identification, RTTI）也经由virtual table被指出来，通常是被在这个表格的第一个slot处。 对于下面的代码： class Point { public: Point(float xval); virtual ~Point(); float x() const; static int PointCount(); protected: virtual &#8230; <a href="http://blog.endlesscode.com/2010/02/21/about-object/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>在C++的对象模型中，nonstatic data members被配置于每一个class object之内，static data members则被存放在所有的class object之外，而static和nonstatic function members也被放在所有的class object之外。Virtual functions则以两个步骤支持之：</p>
<ol>
<li>每一个class产生出一堆指向virtual functions的指针，放在表格之中。这个表格称为virtual table(vtbl)。</li>
<li>每一个class object被添加了一个指针，指向相关的virtual table。通常这个指针被称为vptr。vptr的设定和重置都由每一个class的constructor、destructor和copy assignment运算符自动完成。每一个class所关联的type_info object（用以支持runtime type identification, RTTI）也经由virtual table被指出来，通常是被在这个表格的第一个slot处。</li>
</ol>
<p>对于下面的代码：</p>
<pre class="brush:c++">    class Point
    {
    public:
     Point(float xval);
     virtual ~Point();

     float x() const;
     static int PointCount();

    protected:
     virtual ostream&amp; print(ostream &amp;os) const;

     float _x;
     static int _point_count;
    };</pre>
<p><span id="more-234"></span>其C++的模型大概如下：</p>
<p style="text-align: center;"><img class="size-full wp-image-236 aligncenter" title="c++_obj" src="http://blog.endlesscode.com/wp-content/uploads/2010/02/c++_obj.jpeg" alt="c++_obj" width="476" height="275" /></p>
<p>由上图可以看出，一个class object的需要内存要有：</p>
<ol>
<li> 其nonstatic data members的总和大小。</li>
<li>加上任何由于alignment的需求而填补上去的空间（可能存在于members之间，也可能存在于集合体边界）。</li>
<li>加上为了支持virtual而由内部产生的任何额外负担。</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://blog.endlesscode.com/2010/02/21/about-object/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

