关于强制所有派生类实现特定非虚方法,第二部分
摘要
本文介绍了如何在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& /*value*/,
winrt::Windows::UI::Xaml::Interop::TypeName const& /*targetType*/,
winrt::Windows::Foundation::IInspectable const& /*parameter*/,
winrt::hstring const& /*language*/) <span style="border: solid 1px currentcolor;">= delete</span>;
// 单向转换器无法反向转换
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&amp /*value*/) <span style="border: solid 1px currentcolor;">= delete</span>;
// 单向转换器无法反向转换
Widget ConvertBack(Color const& /*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 &,<wbr />const winrt::<wbr />Windows::<wbr />UI::<wbr />Xaml::<wbr />Interop::<wbr />TypeName &,<wbr />const winrt::<wbr />Windows::<wbr />Foundation::<wbr />IInspectable &,<wbr />const winrt::<wbr />hstring &)': 尝试引用已删除的函数
请参见 '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 &,const winrt::<wbr />Windows::<wbr />UI::<wbr />Xaml::<wbr />Interop::<wbr />TypeName &,const winrt::<wbr />Windows::<wbr />Foundation::<wbr />IInspectable &,<wbr />const winrt::<wbr />hstring &)': <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& /*value*/,
winrt::Windows::UI::Xaml::Interop::TypeName const& /*targetType*/,
winrt::Windows::Foundation::IInspectable const& /*parameter*/,
winrt::hstring const& /*language*/)
= delete<span style="border: solid 1px currentcolor;">("如果你从 OneWayConverter 派生,必须实现 Convert()")</span>;
// 单向转换器无法反向转换
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();
}
};
</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&,<wbr />winrt::<wbr />Windows::<wbr />UI::<wbr />Xaml::<wbr />Interop::<wbr />TypeName const&,<wbr />winrt::<wbr />Windows::<wbr />Foundation::<wbr />IInspectable const&,<wbr />winrt::<wbr />hstring const&)': <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++中强制派生类实现特定非虚拟方法的技术,使用C++/WRL和C++/WinRT的示例,并批评了一个代码审查场景,其中仅使用注释而非适当强制。
C++ 编译器何时可以反虚拟化调用?
探讨 C++ 编译器何时可以对虚函数调用进行去虚拟化,涵盖已知动态类型和 final 关键字等情况,并在 GCC、Clang、MSVC 和 ICC 之间进行比较。
C++26:减少未定义行为
C++26 引入了减少未定义行为的更改,特别是使删除指向不完整类型的指针成为格式错误,从而提高了程序安全性。
通过提取函数的类型相关部分来减少C++模板膨胀,实践示例
本文解释了如何通过提取函数的类型相关部分来减少C++模板膨胀,并使用Windows DispatcherQueueHandler的实践示例来去除代码模板化。
Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 4
Raymond Chen continues his series on creating a fake agile wrapper in COM, explaining how to move non-marshalable objects by forwarding references into the force_marshal wrapper and discussing C++/WinRT deduction guides.