关于强制所有派生类实现特定非虚方法,第二部分

The Old New Thing (Raymond Chen) 新闻

摘要

本文介绍了如何在C++中利用已删除的函数来强制派生类实现特定方法,以提升编译时错误提示并指导开发者。

<p>上次,我们观察到<a title="关于强制所有派生类实现特定非虚方法,第一部分" href="https://devblogs.microsoft.com/oldnewthing/20260827-00/?p=112651">强制所有派生类实现特定非虚方法的一种方式是在基类中根本不实现它</a>,然后坐等编译时问题。但我也指出,我们可以做得更好。</p> <p>问题在于编译器会告诉你哪里出错了,但细节往往隐藏在“补充错误信息”中,可能不容易找到,或者即使找到了,你也不知道如何修复。</p> <p>你可以通过将方法声明为已删除来引导开发者找到正确的错误。</p> <pre>// C++/WRL struct OneWayConverter { // 派生类必须实现 Convert() HRESULT STDMETHODCALLTYPE Convert(IInspectable* value, ABI::Windows::UI::Xaml::Interop::TypeName targetType, IInspectable* parameter, HSTRING language, IInspectable** result) <span style="border: solid 1px currentcolor;">= delete</span>; // 单向转换器无法反向转换 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 { // 派生类必须实现 Convert() 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*/) <span style="border: solid 1px currentcolor;">= delete</span>; // 单向转换器无法反向转换 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(); } }; // 纯 C++ 类似场景 struct OneWayConverter { // 派生类必须实现 Convert() Color Convert(Widget const&amp;amp /*value*/) <span style="border: solid 1px currentcolor;">= delete</span>; // 单向转换器无法反向转换 Widget ConvertBack(Color const&amp; /*color*/) { throw std::exception("not implemented"); } }; </pre> <p>这有几个好处。</p> <p>一是开发者可以看到他们需要实现的确切函数签名:它正是基类中被删除的那个。</p> <p>二是错误信息会指向已删除的函数,如果他们查看那行代码,会看到解释其被删除原因的注释。</p> <pre style="white-space: pre-wrap;">winrt\windows.ui.xaml.data.h(1469,90): error C2280: 'winrt::<wbr />Windows::<wbr />Foundation::<wbr />IInspectable OneWayConverter::<wbr />Convert(<wbr />const winrt::<wbr />Windows::<wbr />Foundation::<wbr />IInspectable &amp;,<wbr />const winrt::<wbr />Windows::<wbr />UI::<wbr />Xaml::<wbr />Interop::<wbr />TypeName &amp;,<wbr />const winrt::<wbr />Windows::<wbr />Foundation::<wbr />IInspectable &amp;,<wbr />const winrt::<wbr />hstring &amp;)': 尝试引用已删除的函数 请参见 'OneWayConverter::Convert' 的声明 test.cpp(60,9): 'winrt::<wbr />Windows::<wbr />Foundation::<wbr />IInspectable OneWayConverter::<wbr />Convert(<wbr />const winrt::<wbr />Windows::<wbr />Foundation::<wbr />IInspectable &amp;,const winrt::<wbr />Windows::<wbr />UI::<wbr />Xaml::<wbr />Interop::<wbr />TypeName &amp;,const winrt::<wbr />Windows::<wbr />Foundation::<wbr />IInspectable &amp;,<wbr />const winrt::<wbr />hstring &amp;)': <span style="border: solid 1px currentcolor;">函数已被显式删除</span> ⟦ 其他错误信息与之前相同 ⟧ </pre> <p>从 C++26 开始,你可以做得更好:你可以直接在 <code>delete</code> 中添加自定义消息!</p> <pre>struct OneWayConverter { // 派生类必须实现 Convert() 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*/) = delete<span style="border: solid 1px currentcolor;">("如果你从 OneWayConverter 派生,必须实现 Convert()")</span>; // 单向转换器无法反向转换 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(); } }; </pre> <p>Microsoft Visual C++ 编译器目前还不支持此功能,但其他编译器支持,并且它们会在主要错误文本中包含自定义消息。</p> <pre style="white-space: pre-wrap;">// clang error: 尝试使用已删除的函数: <span style="border: solid 1px currentcolor;">如果你从 OneWayConverter 派生,必须实现 Convert()</span> // gcc error: use of deleted function 'winrt::<wbr />Windows::<wbr />Foundation::<wbr />IInspectable OneWayConverter::<wbr />Convert(<wbr />winrt::<wbr />Windows::<wbr />Foundation::<wbr />IInspectable const&amp;,<wbr />winrt::<wbr />Windows::<wbr />UI::<wbr />Xaml::<wbr />Interop::<wbr />TypeName const&amp;,<wbr />winrt::<wbr />Windows::<wbr />Foundation::<wbr />IInspectable const&amp;,<wbr />winrt::<wbr />hstring const&amp;)': <span style="border: solid 1px currentcolor;">如果你从 OneWayConverter 派生,必须实现 Convert()</span> </pre> <p>虽然这对 C++/WinRT 和纯 C++ 有效,但对 C++/WRL 无效,因为 WRL 从抽象基类派生,而不能删除基类实现的方法。(大概是因为通过转换为基类,该方法仍然可调用。)</p> <p>因此,<code>delete</code> 技巧仅在您的声明不是基类声明的覆盖时有效。</p> <p>调整实现以提供更好的编译器错误消息是<a title="编译器错误消息元编程:帮助查找冲突的宏定义" href="https://devblogs.microsoft.com/oldnewthing/20211206-00/?p=106002">编译器错误消息元编程</a>的又一个例子,这是编写代码库时被低估的方面之一。</p> <p>文章<a href="https://devblogs.microsoft.com/oldnewthing/20260828-00/?p=112654">关于强制所有派生类实现特定非虚方法,第二部分</a>首次发布于<a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>。</p>
查看原文
查看缓存全文

缓存时间: 2026/08/29 03:51

# 关于强制所有派生类实现特定非虚方法,第二部分 - 新旧事谈 来源:https://devblogs.microsoft.com/oldnewthing/20260828-00?p=112654 上次我们观察到,强制所有派生类实现特定非虚方法的一种方式,是直接在基类中不实现该方法(https://devblogs.microsoft.com/oldnewthing/20260827-00/?p=112651),然后坐等编译时错误爆发。不过我提到,我们可以做得更好。 问题在于编译器虽然告诉你哪里出错了,但细节常常埋没在“补充错误信息”中,而且可能并不显眼,不易挖掘;或者你挖掘出来了,却不清楚如何修复。 你可以通过将方法声明为已删除来引导人们获得正确的错误信息。 ```cpp // C++/WRL struct OneWayConverter { // 派生类必须实现 Convert() HRESULT STDMETHODCALLTYPE Convert(IInspectable* value, ABI::Windows::UI::Xaml::Interop::TypeName targetType, IInspectable* parameter, HSTRING language, IInspectable** result) = delete; // 单向转换器不能反向转换 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 { // 派生类必须实现 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*/) = delete; // 单向转换器不能反向转换 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() Color Convert(Widget const& /*value*/) = delete; // 单向转换器不能反向转换 Widget ConvertBack(Color const& /*color*/) { throw std::exception("not implemented"); } }; ``` 这样做有几个好处。 一是开发者能看到他们需要实现的准确函数签名:就是在基类中被删除的那一个。 二是错误信息会指向那个被删除的函数,如果他们查看那行代码,会看到解释它为何被删除的注释。 ``` winrt\windows.ui.xaml.data.h(1469,90): error C2280: 'winrt::Windows::Foundation::IInspectable OneWayConverter::Convert(const winrt::Windows::Foundation::IInspectable &,const winrt::Windows::UI::Xaml::Interop::TypeName &,const winrt::Windows::Foundation::IInspectable &,const winrt::hstring &)': attempting to reference a deleted function see declaration of 'OneWayConverter::Convert' test.cpp(60,9): 'winrt::Windows::Foundation::IInspectable OneWayConverter::Convert(const winrt::Windows::Foundation::IInspectable &,const winrt::Windows::UI::Xaml::Interop::TypeName &,const winrt::Windows::Foundation::IInspectable &,const winrt::hstring &)': function was explicitly deleted [其他错误信息与之前相同] ``` 从 C++26 开始,你甚至可以做得更好:你可以直接在 `delete` 中放入自定义消息! ```cpp struct 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*/) = delete("If you derive from OneWayConverter, you must implement 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(); } }; ``` Microsoft Visual C++ 编译器尚不支持此功能,但其他编译器支持,并且它们会在主要错误文本中包含自定义消息。 ``` // clang error: attempt to use a deleted function: If you derive from OneWayConverter, you must implement Convert() // gcc error: use of deleted function 'winrt::Windows::Foundation::IInspectable OneWayConverter::Convert(winrt::Windows::Foundation::IInspectable const&,winrt::Windows::UI::Xaml::Interop::TypeName const&,winrt::Windows::Foundation::IInspectable const&,winrt::hstring const&)': If you derive from OneWayConverter, you must implement Convert() ``` 虽然这对 C++/WinRT 和纯 C++ 有效,但对 C++/WRL 却无效,因为 WRL 从抽象基类派生,而你无法删除基类实现的方法。(大概是因为该方法仍然可以通过转换为基类来调用。) 所以 `delete` 技巧仅在你的声明不是对基类声明的重写时才有效。 调整实现以提供更好的编译器错误信息,是编译器错误信息元编程的另一个例子(https://devblogs.microsoft.com/oldnewthing/20211206-00/?p=106002),这是编写代码库时未被充分赏识的方面之一。 ### 分类 ### 主题 ## 作者 Raymond Chen Raymond 参与 Windows 的演进已超过 30 年。2003 年,他开始了一个名为 "The Old New Thing" 的网站,其受欢迎程度远超他最疯狂的想象,这一发展至今仍让他感到不安。该网站催生了一本书,书名也巧合地叫作 "The Old New Thing"(Addison Wesley 2007)。他偶尔会出现在 Windows Dev Docs Twitter 账号上,讲述一些不传达任何有用信息的故事。

相似文章

C++ 编译器何时可以反虚拟化调用?

Hacker News Top

探讨 C++ 编译器何时可以对虚函数调用进行去虚拟化,涵盖已知动态类型和 final 关键字等情况,并在 GCC、Clang、MSVC 和 ICC 之间进行比较。

C++26:减少未定义行为

Lobsters Hottest

C++26 引入了减少未定义行为的更改,特别是使删除指向不完整类型的指针成为格式错误,从而提高了程序安全性。