65.9K
CodeProject 正在变化。 阅读更多。
Home

C++/CLI 中的函数重写

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (30投票s)

2004年6月23日

5分钟阅读

viewsIcon

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::WooDerived::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++ 团队为编译器带来的这些惊人的变化印象深刻。谢谢你们 :-)

© . All rights reserved.