如何强制派生类实现特定非虚方法(第一部分)

The Old New Thing (Raymond Chen) 新闻

摘要

本文探讨了在C++中强制派生类实现特定非虚拟方法的技术,使用C++/WRL和C++/WinRT的示例,并批评了一个代码审查场景,其中仅使用注释而非适当强制。

<p>你可能有一个基类只实现了部分功能,并依赖派生类来完成其余部分。如何确保派生类完成剩余工作?</p> <p>具体来说,假设我们正在实现 <code>IValueConverter</code>,它有两个方法:</p> <ul> <li><code>Convert()</code> 用于从源转换到目标。</li> <li><code>ConvertBack()</code> 以便双向转换可以从目标转换回源。</li> </ul> <p>假设你想编写一个名为 <code>OneWayConverter</code> 的基类。它的实现会使 <code>ConvertBack()</code> 调用失败,并且你想强制派生类实现正向转换。</p> <pre>// C++/WRL struct WidgetColorConverter : Microsoft::WRL::RuntimeClass&lt; Microsoft::WRL::RuntimeClassFlags&lt;Microsoft::WRL::WinRt&gt;, ABI::Windows::UI::Xaml::Data::IValueConverter&gt;, OneWayConverter { // Require the derived class to implement Convert somehow HRESULT STDMETHODCALLTYPE Convert(IInspectable* value, ABI::Windows::UI::Xaml::Interop::TypeName targetType, IInspectable* parameter, HSTRING language, IInspectable** result) { ⟦ ... ⟧ } }; // C++/WinRT struct WidgetColorConverter : winrt::implements&lt;WidgetColorConverter&gt;, OneWayConverter { // Require the derived class to implement Convert somehow winrt::Windows::Foundation::IInspectable Convert( winrt::Windows::Foundation::IInspectable const&amp; value, winrt::Windows::UI::Xaml::Interop::TypeName const&amp; targetType, winrt::Windows::Foundation::IInspectable const&amp; parameter, winrt::hstring const&amp; language) { ⟦ ... ⟧ } } // Plain C++ analogous scenario struct WidgetColorConverter : OneWayConverter { // Require the derived class to implement Convert somehow Color Convert(Widget const&amp;amp value) { ⟦ ... ⟧ } }; </pre> <p>在代码审查中,我看到有人只是通过写注释来尝试这样做。</p> <pre>// C++/WRL struct OneWayConverter { // Derived classes must override this method. HRESULT STDMETHODCALLTYPE Convert(IInspectable* /*value*/, ABI::Windows::UI::Xaml::Interop::TypeName /*targetType*/, IInspectable* /*parameter*/, HSTRING /*language*/, IInspectable** result) { assert(false); *result = nullptr; return E_NOTIMPL; } // One-way converters cannot convert back HRESULT STDMETHODCALLTYPE ConvertBack(IInspectable* /*value*/, ABI::Windows::UI::Xaml::Interop::TypeName /*targetType*/, IInspectable* /*parameter*/, HSTRING /*language*/, IInspectable** result) { *result = nullptr; return E_NOTIMPL; } }; // C++/WinRT struct OneWayConverter { // Derived classes must override this method. winrt::Windows::Foundation::IInspectable Convert( winrt::Windows::Foundation::IInspectable const&amp; /*value*/, winrt::Windows::UI::Xaml::Interop::TypeName const&amp; /*targetType*/, winrt::Windows::Foundation::IInspectable const&amp; /*parameter*/, winrt::hstring const&amp; /*language*/) { assert(false); throw winrt::hresult_not_implemented(); } // One-way converters cannot convert back winrt::Windows::Foundation::IInspectable ConvertBack( winrt::Windows::Foundation::IInspectable const&amp; /*value*/, winrt::Windows::UI::Xaml::Interop::TypeName const&amp; /*targetType*/, winrt::Windows::Foundation::IInspectable const&amp; /*parameter*/, winrt::hstring const&amp; /*language*/) { throw winrt::hresult_not_implemented(); } }; // Plain C++ analogous scenario struct OneWayConverter { // Derived classes must override this method. Color Convert(Widget const&amp;amp /*value*/) { assert(false); throw std::exception("not implemented"); } // One-way converters cannot convert back Widget ConvertBack(Color const&amp; /*color*/) { throw std::exception("not implemented"); } }; </pre> <p>我指出他们做得太多了。</p> <p>强制某人在派生类中实现方法的方法,简单来说就是在基类中根本不实现该方法。</p> <pre>// C++/WRL struct OneWayConverter { // Derived classes must implement Convert() // One-way converters cannot convert back HRESULT STDMETHODCALLTYPE ConvertBack(IInspectable* /*value*/, ABI::Windows::UI::Xaml::Interop::TypeName /*targetType*/, IInspectable* /*parameter*/, HSTRING /*language*/, IInspectable** result) { *result = nullptr; return E_NOTIMPL; } }; // C++/WinRT struct OneWayConverter { // Derived classes must implement Convert() // One-way converters cannot convert back winrt::Windows::Foundation::IInspectable ConvertBack( winrt::Windows::Foundation::IInspectable const&amp; /*value*/, winrt::Windows::UI::Xaml::Interop::TypeName const&amp; /*targetType*/, winrt::Windows::Foundation::IInspectable const&amp; /*parameter*/, winrt::hstring const&amp; /*language*/) { throw winrt::hresult_not_implemented(); } }; // Plain C++ analogous scenario struct OneWayConverter { // Derived classes must implement Convert() // One-way converters cannot convert back Widget ConvertBack(Color const&amp; /*color*/) { throw std::exception("not implemented"); } }; </pre> <p>如果他们忘记实现它,错误消息取决于库。</p> <pre>// C++/WRL struct WidgetColorConverter : Microsoft::WRL::RuntimeClass&lt; Microsoft::WRL::RuntimeClassFlags&lt;Microsoft::WRL::WinRt&gt;, OneWayConverter&gt; { // Oops, forgot to implement Convert() }; </pre> <p>错误在调用 <code>WRL::<wbr />Make</code> 尝试创建有缺陷的 <code>Widget­Color­Converter</code> 时发生。</p> <pre style="white-space: pre-wrap;">wrl\implements.h(2512,32): error C2259: 'WidgetColorConverter': cannot instantiate abstract class see declaration of 'WidgetColorConverter' due to following members: wrl\implements.h(2512,32): 'HRESULT ABI::<wbr />Windows::<wbr />UI::<wbr />Xaml::<wbr />Data::<wbr />IValueConverter::<wbr />Convert(IInspectable *,ABI::<wbr />Windows::<wbr />UI::<wbr />Xaml::<wbr />Interop::<wbr />TypeName,<wbr />IInspectable *,<wbr />HSTRING,<wbr />IInspectable **)': is abstract windows.ui.xaml.data.h(2778,59): see declaration of 'ABI::<wbr />Windows::<wbr />UI::<wbr />Xaml::<wbr />Data::<wbr />IValueConverter::<wbr />Convert' wrl\implements.h(2512,32): the template instantiation context (the oldest one first) is test(41,30): see reference to function template instantiation 'Microsoft::<wbr />WRL::<wbr />ComPtr&lt;<wbr />WidgetColorConverter&gt; Microsoft::<wbr />WRL::<wbr />Details::<wbr />Make&lt;WidgetColorConverter,&gt;(void)' being compiled </pre> <p>“由于以下成员无法实例化抽象类”是未能实现从基类继承的所有必要纯虚方法时的标准错误,因此可以期望遇到此错误的人会理解其含义。</p> <pre>// C++/WinRT struct WidgetColorConverter : winrt::implements&lt;WidgetColorConverter, winrt::Windows::UI::Xaml::Data::IValueConverter&gt;, OneWayConverter { // Oops, forgot to implement Convert() }; </pre> <p>错误在调用 <code>winrt::<wbr />make</code> 时发生
查看原文
查看缓存全文

缓存时间: 2026/08/28 15:50

# 强制所有派生类实现特定非虚方法,第一部分——新旧事物解析 来源:https://devblogs.microsoft.com/oldnewthing/20260827-00?p=112651 你可能有一个只实现部分功能的基类,依赖于派生类来完成剩余工作。如何确保派生类完成剩余部分?为了具体说明,假设我们要实现 `IValueConverter` 接口,该接口包含两个方法: - `Convert()` 用于将源数据转换为目标数据。 - `ConvertBack()` 使双向转换能从目标数据转换回源数据。 假设你想编写一个名为 `OneWayConverter` 的基类。它的实现会让 `ConvertBack()` 调用失败,并且你想强制派生类实现正向转换。 ``` // C++/WRL struct WidgetColorConverter : Microsoft::WRL::RuntimeClass< Microsoft::WRL::RuntimeClassFlags<1>, ABI::Windows::UI::Xaml::Data::IValueConverter>, OneWayConverter { // 要求派生类实现 Convert 方法 HRESULT STDMETHODCALLTYPE Convert( IInspectable* value, ABI::Windows::UI::Xaml::Interop::TypeName targetType, IInspectable* parameter, HSTRING language, IInspectable** result) override { // ... } }; // C++/WinRT struct WidgetColorConverter : winrt::implements<WidgetColorConverter, winrt::Windows::UI::Xaml::Data::IValueConverter>, OneWayConverter { // 要求派生类实现 Convert 方法 winrt::Windows::Foundation::IInspectable Convert( winrt::Windows::Foundation::IInspectable const& value, winrt::Windows::UI::Xaml::Interop::TypeName const& targetType, winrt::Windows::Foundation::IInspectable const& parameter, winrt::hstring const& language) { // ... } }; // 纯C++类似场景 struct WidgetColorConverter : OneWayConverter { // 要求派生类实现 Convert 方法 Color Convert(Widget const& value) { // ... } }; ``` 在代码审查中,我看到有人仅通过编写注释来尝试实现这一点。 ``` // C++/WRL struct OneWayConverter { // 派生类必须重写此方法。 HRESULT STDMETHODCALLTYPE Convert( IInspectable* /*value*/, ABI::Windows::UI::Xaml::Interop::TypeName /*targetType*/, IInspectable* /*parameter*/, HSTRING /*language*/, IInspectable** result) override { assert(false); *result = nullptr; return E_NOTIMPL; } // 单向转换器不能反向转换 HRESULT STDMETHODCALLTYPE ConvertBack( IInspectable* /*value*/, ABI::Windows::UI::Xaml::Interop::TypeName /*targetType*/, IInspectable* /*parameter*/, HSTRING /*language*/, IInspectable** result) override { *result = nullptr; return E_NOTIMPL; } }; // C++/WinRT struct OneWayConverter { // 派生类必须重写此方法。 winrt::Windows::Foundation::IInspectable Convert( winrt::Windows::Foundation::IInspectable const& /*value*/, winrt::Windows::UI::Xaml::Interop::TypeName const& /*targetType*/, winrt::Windows::Foundation::IInspectable const& /*parameter*/, winrt::hstring const& /*language*/) { assert(false); throw winrt::hresult_not_implemented(); } // 单向转换器不能反向转换 winrt::Windows::Foundation::IInspectable ConvertBack( winrt::Windows::Foundation::IInspectable const& /*value*/, winrt::Windows::UI::Xaml::Interop::TypeName const& /*targetType*/, winrt::Windows::Foundation::IInspectable const& /*parameter*/, winrt::hstring const& /*language*/) { throw winrt::hresult_not_implemented(); } }; // 纯C++类似场景 struct OneWayConverter { // 派生类必须重写此方法。 Color Convert(Widget const& /*value*/) { assert(false); throw std::exception("not implemented"); } // 单向转换器不能反向转换 Widget ConvertBack(Color const& /*color*/) { throw std::exception("not implemented"); } }; ``` 我指出他们工作做得过多。强制某人在派生类中实现方法的方法,就是根本不首先在基类中实现该方法。 ``` // C++/WRL struct OneWayConverter { // 派生类必须实现 Convert() // 单向转换器不能反向转换 HRESULT STDMETHODCALLTYPE ConvertBack( IInspectable* /*value*/, ABI::Windows::UI::Xaml::Interop::TypeName /*targetType*/, IInspectable* /*parameter*/, HSTRING /*language*/, IInspectable** result) override { *result = nullptr; return E_NOTIMPL; } }; // C++/WinRT struct OneWayConverter { // 派生类必须实现 Convert() // 单向转换器不能反向转换 winrt::Windows::Foundation::IInspectable ConvertBack( winrt::Windows::Foundation::IInspectable const& /*value*/, winrt::Windows::UI::Xaml::Interop::TypeName const& /*targetType*/, winrt::Windows::Foundation::IInspectable const& /*parameter*/, winrt::hstring const& /*language*/) { throw winrt::hresult_not_implemented(); } }; // 纯C++类似场景 struct OneWayConverter { // 派生类必须实现 Convert() // 单向转换器不能反向转换 Widget ConvertBack(Color const& /*color*/) { throw std::exception("not implemented"); } }; ``` 如果他们忘记实现该方法,错误信息取决于具体的库。 ``` // C++/WRL struct WidgetColorConverter : Microsoft::WRL::RuntimeClass< Microsoft::WRL::RuntimeClassFlags<1>, OneWayConverter> { // 糟糕,忘记实现 Convert() 了 }; ``` 错误发生在尝试调用 `WRL::Make` 创建有缺陷的 `WidgetColorConverter` 时。 ``` wrl\implements.h(2512,32): error C2259: 'WidgetColorConverter': cannot instantiate abstract class see declaration of 'WidgetColorConverter' due to following members: wrl\implements.h(2512,32): 'HRESULT ABI::Windows::UI::Xaml::Data::IValueConverter::Convert(IInspectable *,ABI::Windows::UI::Xaml::Interop::TypeName,IInspectable *,HSTRING,IInspectable **)': is abstract windows.ui.xaml.data.h(2778,59): see declaration of 'ABI::Windows::UI::Xaml::Data::IValueConverter::Convert' wrl\implements.h(2512,32): the template instantiation context (the oldest one first) is test(41,30): see reference to function template instantiation 'Microsoft::WRL::ComPtr Microsoft::WRL::Details::Make<WidgetColorConverter>(void)' being compiled ``` “由于以下成员无法实例化抽象类”是未能实现从基类继承的所有必要纯虚方法时的标准错误,因此可以预期遇到此错误的人会理解其含义。 ``` // C++/WinRT struct WidgetColorConverter : winrt::implements<WidgetColorConverter, winrt::Windows::UI::Xaml::Data::IValueConverter>, OneWayConverter { // 糟糕,忘记实现 Convert() 了 }; ``` 错误发生在尝试调用 `winrt::make` 创建有缺陷的 `WidgetColorConverter` 时。 ``` windows.ui.xaml.data.h(1469,90): error C2039: 'Convert': is not a member of 'WidgetColorConverter' test.cpp(66,8): see declaration of 'WidgetColorConverter' windows.ui.xaml.data.h(1469,90): the template instantiation context (the oldest one first) is test.cpp(66,31): see reference to class template instantiation 'winrt::implements<WidgetColorConverter,winrt::Windows::UI::Xaml::Data::IValueConverter>' being compiled winrt\base.h(8088,31): see reference to class template instantiation 'winrt::impl::producers_base<WidgetColorConverter,winrt::Windows::UI::Xaml::Data::IValueConverter,winrt::Windows::UI::Xaml::Data::IValueConverter>' being compiled winrt\base.h(6763,50): see reference to class template instantiation 'winrt::impl::producer_convert<winrt::Windows::Foundation::IInspectable,winrt::Windows::Foundation::IInspectable>' being compiled winrt\base.h(6734,31): see reference to class template instantiation 'winrt::impl::producer<WidgetColorConverter,winrt::Windows::UI::Xaml::Data::IValueConverter,winrt::implements<WidgetColorConverter,winrt::Windows::UI::Xaml::Data::IValueConverter>>' being compiled winrt\base.h(7137,23): see reference to class template instantiation 'winrt::impl::produce<WidgetColorConverter,winrt::Windows::UI::Xaml::Data::IValueConverter>' being compiled winrt\windows.ui.xaml.data.h(1465,32): while compiling class template member function 'int32_t winrt::impl::produce<WidgetColorConverter,winrt::Windows::UI::Xaml::Data::IValueConverter>::Convert(void *,winrt::impl::struct_Windows_UI_Xaml_Interop_TypeName,void *,void *,void **) noexcept' with [ D=WidgetColorConverter, I=winrt::Windows::UI::Xaml::Data::IValueConverter ] ``` “⟦名称⟧ 不是成员”是 CRTP(奇异递归模板模式)错误的典型表现,因为模板试图调用派生类的方法,但该方法不存在。同样,可以预期遇到此错误的人会理解其含义。 对于纯C++的情况,你可能会遇到: ``` // 纯C++类似场景 struct WidgetColorConverter : OneWayConverter { // 糟糕,忘记实现 Convert() 了 }; ``` 一切都很正常,直到有人尝试调用 `WidgetColorConverter` 上的 `Convert` 方法,而该方法并不存在。 ``` test.cpp(79,20): error C2039: 'Convert': is not a member of 'WidgetColorConverter' ``` 同样,这是C++中常见的错误,因此希望人们能理解其含义。 很好,我们已经能够将所有这些编写错误转化为编译时错误,从而避免了有人使用基类却未能实现所有预期方法的风险。但是等等,我们可以做得更好。我们下次再深入探讨这个问题。 ### 分类 ### 主题 ## 作者 Raymond Chen Raymond 参与了Windows的演进超过30年。2003年,他创建了一个名为 The Old New Thing 的网站,其受欢迎程度远远超出了他的想象,这一发展至今仍让他感到不安。该网站催生了一本书,巧合的是也名为 The Old New Thing(Addison Wesley 2007年出版)。他偶尔会出现在 Windows Dev Docs Twitter 账户上,讲述一些不传达任何有用信息的故事。

相似文章