为什么构造函数的虚拟函数调用有时有效,但其他调用却无效
Why does a virtual function call from constructor work sometimes but not others?
一般经验法则是不要从构造函数中调用虚拟函数,因为这会导致不可预测的行为。那么,为什么它有时会起作用呢?
我最近写了几个带有纯虚拟函数的基类,并意外地在构造函数中包含了对这些函数的间接调用。我意识到我的错误并改正了,但其中一个有效,另一个无效。
以下是有效类的定义:
template <typename TWindow>
class dialog_base
{
static INT_PTR CALLBACK dlg_proc_internal
(HWND,
UINT,
WPARAM,
LPARAM);
protected:
dialog_base
(const LPCWSTR templateName,
const HWND parent)
{
CREATESTRUCT create;
create.lpCreateParams = this;
m_hwnd = CreateDialogParam(
hinstance_, templateName, parent, dlg_proc_internal,
reinterpret_cast<LPARAM>(&create));
}
HWND m_hwnd;
virtual INT_PTR CALLBACK dlg_proc
(UINT,
WPARAM,
LPARAM) = 0;
public:
virtual ~dialog_base()
{
DestroyWindow(m_hwnd);
}
HWND GetHandle() const;
void show() const;
};
在这个类中,DialogBoxParam函数调用dlg_proc_internal
,传递WM_NCCREATE
消息:
template <typename TWindow>
INT_PTR dialog_base<TWindow>::dlg_proc_internal
(HWND hWnd,
UINT msg,
WPARAM wParam,
LPARAM lParam)
{
dialog_base<TWindow>* pThis;
if (msg == WM_NCCREATE)
{
pThis = static_cast<dialog_base<TWindow>*>(reinterpret_cast<
CREATESTRUCT*>(lParam)->lpCreateParams);
SetLastError(0);
if (!SetWindowLongPtr(
hWnd, GWLP_USERDATA,
reinterpret_cast<LONG_PTR>(pThis)) && GetLastError() != 0)
return 0;
}
else
{
pThis = reinterpret_cast<dialog_base<TWindow>*>(
GetWindowLongPtr(hWnd, GWLP_USERDATA));
}
return pThis
? pThis->dlg_proc(msg, wParam, lParam)
: DefWindowProc(hWnd, msg, wParam, lParam);
}
此函数检索作为最后一个参数传递给CreateDialogParam
的指针,并将其存储在窗口中,以便在以后调用该函数时再次检索。然后,它错误地调用了纯虚拟函数dlg_proc
,而不是返回——并且通过子类的构造函数似乎工作得很好。
我创建了一个几乎相同的不同类,只是它称为CreateWindowEx而不是CreateDialogParam
。指针参数以大致相同的方式传递,并用于调用纯虚拟函数。这一次,正如人们所料,它失败了。那么这两种情况有什么区别呢?
编辑:
也许我应该澄清一下。我不是在问"为什么我不能从构造函数调用虚拟成员?"。我想问的是,为什么在构建对象之前解析虚拟成员的过程有时会造成不发生错误的情况,并调用正确的函数。
从构造函数调用virtual
函数在C++中具有完全可预测的行为,就像它在.Net和Java中具有完全可以预测的行为一样。然而,这不是相同的行为。
在C++中,virtual
在调用时对对象的类型执行dispatch函数。其他一些语言将使用预期的类型的对象。两者都是可行的选择,都有风险,但由于这是一个C++问题,我将重点讨论C++风险。
在C++中,virtual
函数可以是纯虚拟函数。问题中的CCD_ 9就是这样一个纯虚拟函数。这些是在基类中声明的,但不一定在基类中定义。试图调用未定义的函数是"未定义行为"。编译人员完全可以随心所欲。
一种可能的实现是编译器只调用一个随机的其他函数。这可能是remove(filename)
。它也可能是派生类的重写。但还有无数其他可能的结果,包括崩溃和挂起。因此,我们不试图预测会发生什么,只是说:不要从actor中调用纯虚拟函数。
脚注:实际上,您可以为纯虚拟功能提供一个实体;语言允许。
CreateDialog...()
(和DialogBox...()
(不会通过WM_(NC)CREATE
将其dwInitParam
参数值传递给您的消息过程。它是通过WM_INITDIALOG
传递的,而您没有处理它。只有CreateWindow/Ex()
通过WM_(NC)CREATE
将其lpParam
参数值传递给消息过程这是有案可查的行为
但更重要的是,您手动将CREATESTRUCT
传递给CreateDialogParam()
。这是不必要的,尤其是因为您没有在WM_NCCREATE
处理程序中处理额外的CREATESTRUCT
。当系统向窗口发出WM_(NC)CREATE
时,传递给CreateWindow/Ex()
的lParam
被封装在CREATESTRUCT
提供的系统中。因此,即使CreateDialogParam()
将其dwInitParam
作为lParam
传递给CreateWindowEx()
(这不是记录行为,BTW(,您仍然无法在消息过程中正确获取dialog_base*
指针,因为您没有处理可能存在的两个单独的CREATESTRUCT
。因此,当出于任何原因使用pThis
指针时,您的代码具有未定义的行为,因为您没有将该指针值正确地传递到消息过程中。
您需要将this
指针直接传递给CreateDialogParam()
而不进行包装,并且需要处理WM_INITDIALOG
而不是WM_NCCREATE
。然后,您的虚拟方法应该按预期运行(即,它不会调度到派生类,因为WM_INITDIALOG
是在基类构造函数的上下文中处理的(。
此外,使用CreateDialog...()
(或DialogBox...()
(时,不要在消息过程中调用DefWindowProc()
(或派生重写(。这在DialogProc
文件中有明确说明:
尽管对话框过程类似于窗口过程,但它不能调用
DefWindowProc
函数来处理不需要的消息。不需要的消息由对话框窗口过程内部处理。
试试这个:
template <typename TWindow>
class dialog_base
{
static INT_PTR CALLBACK dlg_proc_internal(HWND, UINT, WPARAM, LPARAM);
protected:
dialog_base(LPCWSTR templateName, HWND parent)
{
m_hwnd = CreateDialogParamW(hinstance_, templateName, parent, dlg_proc_internal, reinterpret_cast<LPARAM>(this));
}
HWND m_hwnd;
virtual INT_PTR CALLBACK dlg_proc(UINT, WPARAM, LPARAM) = 0;
public:
virtual ~dialog_base()
{
DestroyWindow(m_hwnd);
}
HWND GetHandle() const;
void show() const;
};
template <typename TWindow>
INT_PTR dialog_base<TWindow>::dlg_proc_internal (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
dialog_base<TWindow>* pThis;
if (msg == WM_INITDIALOG)
{
pThis = reinterpret_cast<dialog_base<TWindow>*>(lParam);
// you CANT cancel dialog creation here when
// using CreateDialog...(), only when using
// DialogBox...()! So, no point in doing any
// error checking on SetWindowLongPtr()...
SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis));
// no point in trying to call pThis->dlg_proc()
// here since it won't be dispatched to derived
// classes anyway...
return TRUE; // or FALSE, depending on your needs...
}
pThis = reinterpret_cast<dialog_base<TWindow>*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
if (!pThis) return FALSE;
return pThis->dlg_proc(msg, wParam, lParam);
}
- 为什么我不能在 C++ 中的特定函数重载中调用同一函数的任何其他重载?
- Visual Studio(或任何其他工具)能否将地址解释为调用堆栈(boost上下文)的开头
- 如何封装一个函数,以便它只能由同一类中的一个其他函数调用?
- 如何从其他类n个Qt C++调用QTimer?
- 我想在 Main 中用 C++ 调用其他类中的一个类,但我做不到
- 如何使用 C/C++ 和 system() 系统调用以外的其他方法在 Linux 中获取文件功能?
- Qt信号和插槽如果从QRunnable或其他线程调用,则不起作用
- 没有头文件如何使用c ++调用其他模块中的函数?
- 为什么构造函数的虚拟函数调用有时有效,但其他调用却无效
- 在其他线程中循环访问该concurrent_vector时调用 concurrency::concurrent_vect
- 推断大多数模板对象的参数,但在调用模板函数时对其他对象显式
- 如何引用或调用在 c++ 中的其他 while 循环中创建的向量?
- 如果我具有调用其其他实例之一的超载函数,它是否被认为是递归功能
- 获取对源文件中特定函数的所有调用并生成其他文件(使用 C、C++预处理器或脚本)
- 如何在其他一个C 之前调用函数而不将其初始化
- 如果从在其他函数中调用的函数引发异常会发生什么情况
- 在其他构造函数的调用中调用构造函数时C++编译错误
- 将 2D 数组设置为函数,并根据需要在其他地方调用它
- 在递归功能中,我如何跳到堆栈上的其他功能调用
- CryptoAPI RSA: CryptDecrypt只在第一次解密,其他调用返回NTE_BAD_DATA