335 lines
43 KiB
HTML
335 lines
43 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh" dir="ltr" class="client-nojs" xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh">
|
|
<head><meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<title>virtual 函数说明符</title>
|
|
<link rel="stylesheet" href="ext.css" />
|
|
<link rel="stylesheet" href="site_modules.css" />
|
|
</head>
|
|
<body class="mediawiki ltr sitedir-ltr ns-0 ns-subject page-cpp_language_virtual skin-cppreference2 action-view cpp-navbar">
|
|
<div id="cpp-content-base">
|
|
<div id="content"><a id="top"></a>
|
|
<h1 id="firstHeading" class="firstHeading">virtual 函数说明符</h1>
|
|
<div id="bodyContent">
|
|
<div id="contentSub"><span class="subpages">< <a href="cpp.html">cpp</a>‎ | <a href="cpp-language.html">language</a></span></div>
|
|
<div id="mw-content-text" lang="zh" dir="ltr" class="mw-content-ltr" xml:lang="zh">
|
|
<p><span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="kw1">virtual</span></span></span> 说明符指定非静态<a href="cpp-language-member_functions.html">成员函数</a>为<i>虚</i>函数并支持动态调用派发。它只能在非静态成员函数的首个声明(即当它在类定义中声明时)的 <span class="t-spar">声明说明符序列</span> 中出现。</p>
|
|
<h3><span class="mw-headline" id=".E8.A7.A3.E9.87.8A">解释</span></h3>
|
|
<p>虚函数是其行为可以在派生类中覆盖的成员函数。与非虚函数相反,即使没有关于该类实际类型的编译时信息,进行覆盖的行为仍然保留。当使用到基类的指针或引用来处理派生类时,对被覆盖的虚函数的调用,将会调用定义于派生类中的行为。这种函数调用被称为<i>虚函数调用</i>或<i>虚调用</i>。虚函数调用在使用<a href="cpp-language-lookup.html">有限定名字查找</a>(即函数名出现在作用域解析运算符 <code>::</code> 的右侧)时被抑制。</p>
|
|
<div class="t-example">
|
|
<div class="t-example-live-link"></div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co2">#include <iostream></span>
|
|
<span class="kw1">struct</span> Base <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"基<span class="es1">\n</span>"</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> Derived <span class="sy4">:</span> Base <span class="br0">{</span>
|
|
<span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span> override <span class="br0">{</span> <span class="co1">// 'override' 可选</span>
|
|
<a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"派生<span class="es1">\n</span>"</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
Base b<span class="sy4">;</span>
|
|
Derived d<span class="sy4">;</span>
|
|
|
|
<span class="co1">// 通过引用调用虚函数</span>
|
|
Base<span class="sy3">&</span> br <span class="sy1">=</span> b<span class="sy4">;</span> <span class="co1">// br 的类型是 Base&</span>
|
|
Base<span class="sy3">&</span> dr <span class="sy1">=</span> d<span class="sy4">;</span> <span class="co1">// dr 的类型也是 Base&</span>
|
|
br.<span class="me1">f</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 打印 "基"</span>
|
|
dr.<span class="me1">f</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 打印 "派生"</span>
|
|
|
|
<span class="co1">// 通过指针调用虚函数</span>
|
|
Base<span class="sy2">*</span> bp <span class="sy1">=</span> <span class="sy3">&</span>b<span class="sy4">;</span> <span class="co1">// bp 的类型是 Base*</span>
|
|
Base<span class="sy2">*</span> dp <span class="sy1">=</span> <span class="sy3">&</span>d<span class="sy4">;</span> <span class="co1">// dp 的类型也是 Base*</span>
|
|
bp<span class="sy2">-</span><span class="sy1">></span>f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 打印 "基"</span>
|
|
dp<span class="sy2">-</span><span class="sy1">></span>f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 打印 "派生"</span>
|
|
|
|
<span class="co1">// 非虚函数调用</span>
|
|
br.<span class="me1">Base</span><span class="sy4">::</span><span class="me2">f</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 打印 "基"</span>
|
|
dr.<span class="me1">Base</span><span class="sy4">::</span><span class="me2">f</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 打印 "基"</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
</div>
|
|
<h3><span class="mw-headline" id=".E7.BB.86.E8.8A.82">细节</span></h3>
|
|
<p>如果某个成员函数 <code>vf</code> 在类 <code>Base</code> 中被声明为 <code>virtual</code>,且某个直接或间接派生于 <code>Base</code> 的类 <code>Derived</code> 拥有一个下列几项与之相同的成员函数声明:</p>
|
|
<ul>
|
|
<li>名字</li>
|
|
<li>形参列表(但非返回类型)</li>
|
|
<li>cv 限定符</li>
|
|
<li>引用限定符</li>
|
|
</ul>
|
|
<p>那么 <code>vf</code> 在类 <code>Derived</code> 中也是<i>虚</i>函数(无论其声明中是否使用关键词 <code>virtual</code>)并<i>覆盖</i> Base::vf(无论其声明中是否使用单词 <code>override</code>)。</p>
|
|
<p>要覆盖的 <code>Base::vf</code> 不需要可访问或可见。(<code>Base::vf</code> 能声明为私有或者私有继承 <code>Base</code>。 <code>Derived</code> 的基类中的任何同名成员不妨碍覆盖确定,即使在名字查找时它们会隐藏 <code>Base::vf</code>。)</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">class</span> B <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> do_f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 私有成员</span>
|
|
<span class="kw1">public</span><span class="sy4">:</span>
|
|
<span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span> do_f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="br0">}</span> <span class="co1">// 公开接口</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> D <span class="sy4">:</span> <span class="kw1">public</span> B <span class="br0">{</span>
|
|
<span class="kw4">void</span> do_f<span class="br0">(</span><span class="br0">)</span> override<span class="sy4">;</span> <span class="co1">// 覆盖 B::do_f</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
D d<span class="sy4">;</span>
|
|
B<span class="sy2">*</span> bp <span class="sy1">=</span> <span class="sy3">&</span>d<span class="sy4">;</span>
|
|
bp<span class="sy2">-</span><span class="sy1">></span>f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 内部调用 D::do_f();</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>每个虚函数都有<i>最终覆盖函数</i>,它是进行虚函数调用时所执行的函数。基类 <code>Base</code> 的虚成员函数 <code>vf</code> 是最终覆盖函数,除非派生类声明或(通过多重继承)继承了覆盖 <code>vf</code> 的另一个函数。</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">struct</span> A <span class="br0">{</span> <span class="kw1">virtual</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="br0">}</span><span class="sy4">;</span> <span class="co1">// A::f 是虚函数</span>
|
|
<span class="kw1">struct</span> B <span class="sy4">:</span> A <span class="br0">{</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="br0">}</span><span class="sy4">;</span> <span class="co1">// B 中的 B::f 覆盖 A::f</span>
|
|
<span class="kw1">struct</span> C <span class="sy4">:</span> <span class="kw1">virtual</span> B <span class="br0">{</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="br0">}</span><span class="sy4">;</span> <span class="co1">// C 中的 C::f 覆盖 A::f</span>
|
|
<span class="kw1">struct</span> D <span class="sy4">:</span> <span class="kw1">virtual</span> B <span class="br0">{</span><span class="br0">}</span><span class="sy4">;</span> <span class="co1">// D 不引入覆盖函数,最终覆盖函数是 B::f</span>
|
|
<span class="kw1">struct</span> E <span class="sy4">:</span> C, D <span class="br0">{</span> <span class="co1">// E 不引入覆盖函数,最终覆盖函数是 C::f</span>
|
|
<span class="kw1">using</span> A<span class="sy4">::</span><span class="me2">f</span><span class="sy4">;</span> <span class="co1">// 非函数声明,只为了能让 A::f 被查找到</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
E e<span class="sy4">;</span>
|
|
e.<span class="me1">f</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 虚调用 e 中的最终覆盖函数 C::f</span>
|
|
e.<span class="me1">E</span><span class="sy4">::</span><span class="me2">f</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 非虚调用调用在 E 中可见的 A::f</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>如果一个函数拥有多于一个最终覆盖函数,那么程序非良构:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">struct</span> A <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> VB1 <span class="sy4">:</span> <span class="kw1">virtual</span> A <span class="br0">{</span>
|
|
<span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 覆盖 A::f</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> VB2 <span class="sy4">:</span> <span class="kw1">virtual</span> A <span class="br0">{</span>
|
|
<span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 覆盖 A::f</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="co1">// struct Error : VB1, VB2 {</span>
|
|
<span class="co1">// // 错误:A::f 在 Error 中拥有两个最终覆盖函数</span>
|
|
<span class="co1">// };</span>
|
|
<span class="kw1">struct</span> Okay <span class="sy4">:</span> VB1, VB2 <span class="br0">{</span>
|
|
<span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// OK:这是 A::f 的最终覆盖函数</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> VB1a <span class="sy4">:</span> <span class="kw1">virtual</span> A <span class="br0">{</span><span class="br0">}</span><span class="sy4">;</span> <span class="co1">// 不声明覆盖函数</span>
|
|
<span class="kw1">struct</span> Da <span class="sy4">:</span> VB1a, VB2 <span class="br0">{</span>
|
|
<span class="co1">// 在 Da 中,A::f 的最终覆盖函数是 VB2::f</span>
|
|
<span class="br0">}</span><span class="sy4">;</span></pre></div>
|
|
</div>
|
|
<p>名字相同但形参列表不同的函数并不覆盖同名的基类函数,但会<i>隐藏</i>它:在<a href="cpp-language-lookup.html">无限定名字查找</a>检查派生类的作用域时,查找找到该声明就不会再检查基类。</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">struct</span> B <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> D <span class="sy4">:</span> B <span class="br0">{</span>
|
|
<span class="kw4">void</span> f<span class="br0">(</span><span class="kw4">int</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// D::f 隐藏 B::f(错误的形参列表)</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> D2 <span class="sy4">:</span> D <span class="br0">{</span>
|
|
<span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// D2::f 覆盖 B::f(不管它是否可见)</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
B b<span class="sy4">;</span> B<span class="sy3">&</span> b_as_b <span class="sy1">=</span> b<span class="sy4">;</span>
|
|
D d<span class="sy4">;</span> B<span class="sy3">&</span> d_as_b <span class="sy1">=</span> d<span class="sy4">;</span> D<span class="sy3">&</span> d_as_d <span class="sy1">=</span> d<span class="sy4">;</span>
|
|
D2 d2<span class="sy4">;</span> B<span class="sy3">&</span> d2_as_b <span class="sy1">=</span> d2<span class="sy4">;</span> D<span class="sy3">&</span> d2_as_d <span class="sy1">=</span> d2<span class="sy4">;</span>
|
|
|
|
b_as_b.<span class="me1">f</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 调用 B::f()</span>
|
|
d_as_b.<span class="me1">f</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 调用 B::f()</span>
|
|
d2_as_b.<span class="me1">f</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 调用 D2::f()</span>
|
|
|
|
d_as_d.<span class="me1">f</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 错误:D 中的查找只找到 f(int)</span>
|
|
d2_as_d.<span class="me1">f</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 错误:D 中的查找只找到 f(int)</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-since-cxx11">
|
|
<td>
|
|
<p>如果函数以说明符 <code>override</code> 声明,但不覆盖任何虚函数,那么程序非良构:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">struct</span> B <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="kw4">int</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> D <span class="sy4">:</span> B <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="kw4">int</span><span class="br0">)</span> override<span class="sy4">;</span> <span class="co1">// OK,D::f(int) 覆盖 B::f(int)</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="kw4">long</span><span class="br0">)</span> override<span class="sy4">;</span> <span class="co1">// 错误:f(long) 不覆盖 B::f(int)</span>
|
|
<span class="br0">}</span><span class="sy4">;</span></pre></div>
|
|
</div>
|
|
<p>如果函数以说明符 <code>final</code> 声明,而被另一函数试图覆盖,那么程序非良构:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">struct</span> B <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span> <span class="kw4">const</span> final<span class="sy4">;</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> D <span class="sy4">:</span> B <span class="br0">{</span>
|
|
<span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span> <span class="kw4">const</span><span class="sy4">;</span> <span class="co1">// 错误:D::f 试图覆盖 final B::f</span>
|
|
<span class="br0">}</span><span class="sy4">;</span></pre></div>
|
|
</div>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx11">(C++11 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<p>非成员函数和静态成员函数不能是虚函数。</p>
|
|
<p>函数模板不能被声明为 <code>virtual</code>。这只适用于自身是模板的函数——类模板的常规成员函数可以被声明为虚函数。</p>
|
|
<table class="t-rev-begin">
|
|
<tr class="t-rev t-since-cxx20">
|
|
<td>
|
|
<p>虚函数(无论是声明为 virtual 者还是覆盖函数)不能有任何关联制约。</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">struct</span> A <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span> requires <span class="kw2">true</span><span class="sy4">;</span> <span class="co1">// 错误:受制约的虚函数</span>
|
|
<span class="br0">}</span><span class="sy4">;</span></pre></div>
|
|
</div>
|
|
<p><a href="cpp-language-consteval.html"><code>consteval</code></a> 虚函数不能覆盖非 <code>consteval</code> 虚函数或被它覆盖。</p>
|
|
</td>
|
|
<td><span class="t-mark-rev t-since-cxx20">(C++20 起)</span></td>
|
|
</tr>
|
|
</table>
|
|
<p>在编译时替换虚函数的<a href="cpp-language-default_arguments.html">默认实参</a>。</p>
|
|
<h4><span class="mw-headline" id=".E5.8D.8F.E5.8F.98.E8.BF.94.E5.9B.9E.E7.B1.BB.E5.9E.8B">协变返回类型</span></h4>
|
|
<p>如果函数 <code>Derived::f</code> 覆盖 <code>Base::f</code>,那么它的返回类型必须要么相同,要么为<i>协变(covariant)</i>。当满足以下所有要求时,两个类型为协变:</p>
|
|
<ul>
|
|
<li>两个类型都是到类的指针或引用(左值或右值)。不允许多级指针或引用。</li>
|
|
<li><code>Base::f()</code> 的返回类型中被引用/指向的类,必须是 <code>Derived::f()</code> 的返回类型中被引用/指向的类的无歧义且可访问的直接或间接基类。</li>
|
|
<li><code>Derived::f()</code> 的返回类型必须有相对于 <code>Base::f()</code> 的返回类型的相等或较少的 <a href="cpp-language-cv.html">cv 限定</a>。</li>
|
|
</ul>
|
|
<p><code>Derived::f</code> 的返回类型中的类必须要么是 <code>Derived</code> 自身,要么必须是在 <code>Derived::f</code> 声明点的某个<a href="cpp-language-incomplete_type.html" class="mw-redirect">完整类型</a>。</p>
|
|
<p>进行虚函数调用时,最终覆盖函数的返回类型被<a href="cpp-language-implicit_conversion.html">隐式转换</a>成所调用的被覆盖函数的返回类型:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">class</span> B <span class="br0">{</span><span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw1">struct</span> Base <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> vf1<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> vf2<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> vf3<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw1">virtual</span> B<span class="sy2">*</span> vf4<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw1">virtual</span> B<span class="sy2">*</span> vf5<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw1">class</span> D <span class="sy4">:</span> <span class="kw1">private</span> B <span class="br0">{</span>
|
|
<span class="kw1">friend</span> <span class="kw1">struct</span> Derived<span class="sy4">;</span> <span class="co1">// 在 Derived 中,B 是 D 的可访问基类</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw1">class</span> A<span class="sy4">;</span> <span class="co1">// 前置声明的类是不完整类型</span>
|
|
|
|
<span class="kw1">struct</span> Derived <span class="sy4">:</span> <span class="kw1">public</span> Base <span class="br0">{</span>
|
|
<span class="kw4">void</span> vf1<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 虚函数,覆盖 Base::vf1()</span>
|
|
<span class="kw4">void</span> vf2<span class="br0">(</span><span class="kw4">int</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 非虚函数,隐藏 Base::vf2()</span>
|
|
<span class="co1">// char vf3(); // 错误:覆盖 Base::vf3,但具有不同且非协变的返回类型</span>
|
|
D<span class="sy2">*</span> vf4<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 覆盖 Base::vf4() 并具有协变的返回类型</span>
|
|
<span class="co1">// A* vf5(); // 错误:A 是不完整类型</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
Derived d<span class="sy4">;</span>
|
|
Base<span class="sy3">&</span> br <span class="sy1">=</span> d<span class="sy4">;</span>
|
|
Derived<span class="sy3">&</span> dr <span class="sy1">=</span> d<span class="sy4">;</span>
|
|
|
|
br.<span class="me1">vf1</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 调用 Derived::vf1()</span>
|
|
br.<span class="me1">vf2</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 调用 Base::vf2()</span>
|
|
<span class="co1">// dr.vf2(); // 错误:vf2(int) 隐藏 vf2()</span>
|
|
|
|
B<span class="sy2">*</span> p <span class="sy1">=</span> br.<span class="me1">vf4</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 调用 Derived::vf4() 并将结果转换为 B*</span>
|
|
D<span class="sy2">*</span> q <span class="sy1">=</span> dr.<span class="me1">vf4</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 调用 Derived::vf4() 而不将结果转换为 B*</span>
|
|
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<h4><span class="mw-headline" id=".E8.99.9A.E6.9E.90.E6.9E.84.E5.87.BD.E6.95.B0">虚析构函数</span></h4>
|
|
<p>虽然析构函数不会继承,但是如果基类将其析构函数声明为 <code>virtual</code>,那么派生的析构函数始终覆盖它。这样就可以通过指向基类的指针 delete 动态分配的多态类型对象。</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">class</span> Base <span class="br0">{</span>
|
|
<span class="kw1">public</span><span class="sy4">:</span>
|
|
<span class="kw1">virtual</span> ~Base<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span> <span class="coMULTI">/* 释放 Base 的资源 */</span> <span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw1">class</span> Derived <span class="sy4">:</span> <span class="kw1">public</span> Base <span class="br0">{</span>
|
|
~Derived<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span> <span class="coMULTI">/* 释放 Derived 的资源 */</span> <span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
Base<span class="sy2">*</span> b <span class="sy1">=</span> new Derived<span class="sy4">;</span>
|
|
delete b<span class="sy4">;</span> <span class="co1">// 进行对 Base::~Base() 的虚函数调用</span>
|
|
<span class="co1">// 由于它是虚函数,因此它调用的是 Derived::~Derived(),</span>
|
|
<span class="co1">// 这就能释放派生类的资源,然后遵循通常的析构顺序</span>
|
|
<span class="co1">// 调用 Base::~Base()</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>此外,如果基类的析构函数非虚,则通过指向该基类的指针删除派生类对象是<i>未定义行为</i>,无论不调用派生的析构函数时是否会导致资源泄漏<span class="t-rev-inl t-since-cxx20"><span>,除非选择的解分配函数是销毁的 <span class="t-lc"><a href="cpp-memory-new-operator_delete.html">operator delete</a></span></span> <span><span class="t-mark-rev t-since-cxx20">(C++20 起)</span></span></span>。</p>
|
|
<p>一条有用的方针是,凡是当涉及 delete 表达式<span class="t-rev-inl t-since-cxx11"><span>,例如在 <span class="t-lc"><a href="cpp-memory-unique_ptr.html">std::unique_ptr</a></span> 中隐式使用</span> <span><span class="t-mark-rev t-since-cxx11">(C++11 起)</span></span></span>时,任何基类的析构函数必须为<a rel="nofollow" class="external text" href="https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#discussion-make-base-class-destructors-public-and-virtual-or-protected-and-nonvirtual">公开且虚,或受保护且非虚</a>。</p>
|
|
<h3><span class="mw-headline" id=".E5.9C.A8.E6.9E.84.E9.80.A0.E5.92.8C.E6.9E.90.E6.9E.84.E6.9C.9F.E9.97.B4">在构造和析构期间</span></h3>
|
|
<p>当从构造函数或从析构函数中直接或间接调用虚函数(包括在类的非静态数据成员的构造或析构期间,例如在成员<a href="cpp-language-initializer_list.html" class="mw-redirect">初始化器列表</a>中),且对其实施调用的对象是正在构造或析构中的对象时,所调用的函数是构造函数或析构函数的类中的最终覆盖函数,而非进一步的派生类中的覆盖函数。 换言之,在构造和析构期间,进一步的派生类并不存在。</p>
|
|
<p>当构建具有多个分支的复杂类时,在属于一个分支的构造函数内,多态被限制到该类及其基类:如果它获得了指向这个子层级之外的某个基类子对象的指针或引用并试图进行虚函数调用(例如通过显式成员访问),那么行为未定义:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="kw1">struct</span> V <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> g<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="kw1">struct</span> A <span class="sy4">:</span> <span class="kw1">virtual</span> V <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 在 A 中,V::f 的最终覆盖函数是 A::f</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> B <span class="sy4">:</span> <span class="kw1">virtual</span> V <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> g<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 在 B 中,V::g 的最终覆盖函数是 B::g</span>
|
|
B<span class="br0">(</span>V<span class="sy2">*</span>, A<span class="sy2">*</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">struct</span> D <span class="sy4">:</span> A, B <span class="br0">{</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 在 D 中,V::f 的最终覆盖函数是 D::f</span>
|
|
<span class="kw1">virtual</span> <span class="kw4">void</span> g<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 在 D 中,V::g 的最终覆盖函数是 D::g</span>
|
|
|
|
<span class="co1">// 注意:A 在 B 之前初始化</span>
|
|
D<span class="br0">(</span><span class="br0">)</span> <span class="sy4">:</span> B<span class="br0">(</span><span class="br0">(</span>A<span class="sy2">*</span><span class="br0">)</span>this, this<span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
<span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
<span class="co1">// B 的构造函数,从 D 的构造函数调用 </span>
|
|
B<span class="sy4">::</span><span class="me2">B</span><span class="br0">(</span>V<span class="sy2">*</span> v, A<span class="sy2">*</span> a<span class="br0">)</span>
|
|
<span class="br0">{</span>
|
|
f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 对 V::f 的虚调用(尽管 D 拥有最终覆盖函数,D 也不存在)</span>
|
|
g<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// 对 B::g 的虚调用,在 B 中是最终覆盖函数</span>
|
|
|
|
v<span class="sy2">-</span><span class="sy1">></span>g<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// v 的类型 V 是 B 的基类,虚调用像之前一样调用 B::g</span>
|
|
|
|
a<span class="sy2">-</span><span class="sy1">></span>f<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="co1">// a 的类型 A 不是 B 的基类,它属于层级中的不同分支。</span>
|
|
<span class="co1">// 尝试通过这个分支进行虚调用导致未定义行为,</span>
|
|
<span class="co1">// 即使此时 A 已完成构造</span>
|
|
<span class="co1">// (它在 B 之前构造,因为它在 D 的基类列表中在 B 前面出现)</span>
|
|
<span class="co1">// 实践中,对 A::f 的虚调用会试图使用 B 的虚成员函数表,</span>
|
|
<span class="co1">// 因为它在 B 的构造期间是活跃的</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<h3><span class="mw-headline" id=".E5.8F.82.E9.98.85">参阅</span></h3>
|
|
<table class="t-dsc-begin">
|
|
<tr class="t-dsc">
|
|
<td colspan="2"><a href="cpp-language-derived_class.html">派生类与继承模式</a></td>
|
|
</tr>
|
|
<tr class="t-dsc">
|
|
<td><a href="cpp-language-override.html"><code>override</code> 说明符</a><span class="t-mark-rev t-since-cxx11">(C++11)</span></td>
|
|
<td>显式说明方法覆盖另一方法</td>
|
|
</tr>
|
|
<tr class="t-dsc">
|
|
<td><a href="cpp-language-final.html"><code>final</code> 说明符</a><span class="t-mark-rev t-since-cxx11">(C++11)</span></td>
|
|
<td>声明不能被覆盖的方法</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="visualClear"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|