C++/CLI 中的函数重写






4.81/5 (30投票s)
2004年6月23日
5分钟阅读

338933
探讨了显式重写、重命名重写、多重重写和封印方法等新功能
引言
本文将介绍 C++/CLI 在函数重载方面提供的一些新功能。截至撰写本文时,作者仅能接触到编译器的 Alpha 版本,因此此处展示的一些代码片段语法在最终发布版(预计在 2005 年 6 月之后发布)中可能会发生变化。当然,当作者获得新版本编译器时,文章将进行更新,以反映与本文描述略有不同的语法。读者应了解基本的 C++/CLI 语法,如果不了解,作者强烈建议他们参考作者的一篇文章—— C++/CLI 初探;该文章提供了对 Microsoft 引入的 C++/CLI 新语法的基本介绍,从 VC++ Whidbey 开始。
显式重写
在原生 C++ 中,一个派生类函数,如果它与基类 virtual
函数具有相同的名称和参数,那么它 *总是* 会重写该函数。在 C++/CLI 中,您可以使用 new
上下文关键字来指定是想重写基类函数还是隐藏它。
顺便提一下,我使用了以下 #define
,因为我不想反复输入(甚至复制/粘贴)Console::WriteLine
。
#define Show(x) Console::WriteLine(x)
以下代码片段演示了 new
上下文关键字的用法。
ref class Base
{
public:
virtual void Goo()
{
Show("Base::Goo");
}
virtual void Boo()
{
Show("Base::Boo");
}
virtual void Doo()
{
Show("Base::Doo");
}
};
ref class Derived : Base
{
public:
//Overrides Base::Goo
virtual void Goo()
{
Show("Derived::Goo");
}
//Overrides Base::Boo as above
virtual void Boo() = Base::Boo
{
Show("Derived::Boo");
}
//Hides Base::Doo
virtual void Doo() new
{
Show("Derived::Doo");
}
};
下面是一些调用上面方法上引用 Derived
对象的 Base
句柄的示例代码。
void _tmain()
{
Base^ r = gcnew Derived();
r->Goo();
r->Boo();
r->Doo();
}
您将得到以下输出:-
Derived::Goo
Derived::Boo
Base::Doo
让我们看一下这些方法生成的 IL 的一部分:-
Derived::Goo
的 IL
.method public virtual instance void Goo() cil managed
{
.maxstack 1
//...
IL_000a: ret
}
好的,这段代码没什么特别的,一切看起来都很正常 :-)
Derived::Boo
的 IL
.method public newslot virtual final instance void
Boo() cil managed
{
.override Base::Boo
.maxstack 1
//...
IL_000a: ret
}
嗯,注意到生成的 .override
IL 指令了吗?它指定了一个新的 virtual
方法,该方法使用与基类相同的签名,但名称不同,而这个名称由 .override
指令指定。当然,在本例中它并没有真正影响结果,因为它再次简单地引用了与派生类方法名称相同的基类方法名称。稍后,当我们讨论重命名重写时,您会看到 .override
指令更合适的用法。
Derived::Doo
的 IL
.method public newslot virtual instance void
Doo() cil managed
{
.maxstack 1
//...
IL_000a: ret
}
注意到方法定义中使用了 newslot
IL 关键字。如果任何方法定义被标记为 newslot
,那么它总是创建一个新的 virtual
方法,即使其基类定义了一个同名同参数列表的 virtual
方法。
重命名重写
原生 C++ 要求派生类方法的名称必须与它所重写的基类 virtual
方法的名称匹配。C++/CLI 允许我们使用一个派生类方法重写基类 virtual
方法,即使派生类方法的名称与基类方法的名称不匹配。当然,方法签名必须等效。以下代码片段应该能让事情更清楚。
ref class Base
{
public:
virtual void Goo()
{
Show("Base::Goo");
}
virtual void Boo()
{
Show("Base::Boo");
}
};
ref class Derived : Base
{
public:
//Overrides Base::Goo
virtual void Goo()
{
Show("Derived::Goo");
}
//Overrides Base::Boo
virtual void Woo() = Base::Boo
{
Show("Derived::Woo");
}
//New function Boo in Derived
virtual void Boo() new
{
Show("Derived::Boo");
}
};
调用这些方法的代码:-
void _tmain()
{
Base^ r = gcnew Derived();
r->Goo();
r->Boo();
Derived^ d = dynamic_cast<Derived^>(r);
d->Goo();
d->Boo();
d->Woo();
}
输出将是:-
Derived::Goo
Derived::Woo
Derived::Goo
Derived::Boo
Derived::Woo
让我们看一下 Derived::Woo
和 Derived::Boo
的 IL。
Derived::Woo
的 IL
.method public newslot virtual final instance void
Woo() cil managed
{
.override Base::Boo
.maxstack 1
//...
IL_000a: ret
}
如前所述,IL .override
指令允许我们生成一个派生类方法,该方法将重写一个名称不同但签名相同的基类 virtual
方法。您还会注意到 newslot
关键字已添加到方法定义中,因此会生成一个新的 virtual
方法。这样,即使基类具有同名方法,派生类方法实际上也将是一个新的 virtual
方法,它重写了与同名方法不同的基类 virtual
方法。
Derived::Boo
的 IL
.method public newslot virtual instance void
Boo() cil managed
{
.maxstack 1
//...
IL_000a: ret
} // end of method Derived::Boo
如果我们没有将 Derived::Boo
指定为 new
方法,编译器将报错,因为 Base::Boo
已被 Derived::Woo
重写:-
error C3663: 'Derived::Boo' :
method implicitly overrides 'Base::Boo'
which has already been explicitly overridden
但由于我们已指定此函数是一个新的 virtual
函数,因此生成的 IL 在方法定义中使用了 IL newslot
关键字。
替代语法
一种看起来更简洁的语法替代方法是使用上下文标识符 override
,如下所示:-
ref class Derived : Base
{
public:
//...
//Override Base::Boo
virtual void Woo() override = Base::Boo
{
Show("Derived::Woo");
}
//...
};
多重重写
在原生 C++ 中,继承自多个基类/接口的类的函数,只有在所有基类/接口都有同名同签名函数时,才能重写多个基类函数。C++/CLI 允许您指定哪个方法重写哪个基类/接口方法,前提是函数签名匹配。
以下代码片段演示了多重重写:-
interface class INish
{
void Goo();
};
interface class IBuster
{
void Boo();
void Moo();
};
ref class Base
{
public:
virtual void Goo()
{
Show("Base::Goo");
}
virtual void Boo()
{
Show("Base::Boo");
}
};
ref class Derived : Base, INish, IBuster
{
public:
//Overrides both Base::Goo, INish::Goo, IBuster::Moo
virtual void Goo() = Base::Goo, INish::Goo, IBuster::Moo
{
Show("Derived::Goo");
}
//Overrides Base::Boo
virtual void Boo() = Base::Boo
{
Show("Derived::Boo");
}
//Override IBuster::Boo
virtual void Hoo()= IBuster::Boo
{
Show("Derived::Hoo");
}
};
使用以下代码片段调用这些方法:-
void _tmain()
{
Base^ r = gcnew Derived();
r->Goo();
r->Boo();
INish^ i = dynamic_cast<INish^>(r);
i->Goo();
IBuster^ b = dynamic_cast<IBuster^>(r);
b->Boo();
b->Moo();
}
输出将是:-
Derived::Goo
Derived::Boo
Derived::Goo
Derived::Hoo
Derived::Goo
让我们看看生成的 IL 是什么样子的。
Derived::Goo
的 IL
.method public newslot virtual final instance void
Goo() cil managed
{
.override IBuster::Moo
.override INish::Goo
.override Base::Goo
.maxstack 1
//...
IL_000a: ret
}
好吧,这没什么大不了的,对吧?所做的就是多次使用 .override
IL 指令,每次指定一个不同的基类或接口。
封印函数以防止进一步重写
有时我们可能希望某个函数在继承链的更低层级不被重写。这时 sealed
函数修饰符就派上用场了。
ref class Base
{
public:
virtual void Goo() sealed
{
Show("Base::Goo");
}
};
ref class Derived : Base
{
public:
virtual void Goo() //won't compile
{
Show("Derived::Goo");
}
};
编译器将抛出一个错误:-
error C3248:
'Base::Goo': function declared as 'sealed'
cannot be overridden by 'Derived::Goo'
当然,您可以通过使用 new
函数修饰符使其编译。
ref class Base
{
public:
virtual void Goo() sealed
{
Show("Base::Goo");
}
};
ref class Derived : Base
{
public:
virtual void Goo() new
{
Show("Derived::Goo");
}
};
但是如果您这样做,那么以下代码片段:-
void _tmain()
{
Base^ r = gcnew Derived();
r->Goo();
}
...将产生以下输出:-
Base::Goo
...因为 Derived::Goo
现在是一个新函数,并且生成的 IL 将把 newslot
关键字应用于方法定义。
abstract
函数修饰符
ref class Base
{
public:
virtual void Goo() abstract;
virtual void Boo()
{
Show("Base::Boo");
}
};
行 virtual void Goo() abstract;
在语法上等同于 virtual void Goo() = 0;
,而 abstract
函数修饰符仅用于提供统一的语法风格。
结论
正如引言中所述,本文基于我使用我能找到的最新 Alpha 编译器版本的经验。文章中展示的一些语法在 VC++.NET Whidbey 正式发布时可能会发生变化。但无论如何,我必须说,我对 VC++ 团队为编译器带来的这些惊人的变化印象深刻。谢谢你们 :-)