何时一个类的指针可以强制转化为另外一个类的指针,即使它们之间没有派生关系?

博客围绕C++中MFC类指针转换展开,探讨了在何种情况下可安全将一个类指针转化为另一个类指针,如MFC从CWnd到CEdit的派生过程。还提及强制转换副作用,以及MFC中多继承的麻烦和采用的替代方法。

问:我看到CListView的成员函数GetListCtrl直接把CListView本身的指针转换为CListCtrl指针。我想知道在什么情况下可以安全地把一个类的指针转化为另一个类的指针?
答:只要你访问的数据的内存表示是完全相同的,那么这种转化就是安全的。考虑MFC从CWnd到CEdit的派生过程,是一个纯粹的封装——既没有增加数据成员也没有增加虚函数(隐含的虚函数表的指针实际上是类的一个成员,你可以在VC的监视窗口看到)——所有的代码只是在操作基类的成员。所以你可以在对话框的成员函数中安全地进行如下调用
CEdit* pEdit=GetDlgItem (IDC_EDIT);//把CTempWnd类型的对象的指针转化成CEdit指针
也就是说,你可以安全地把CWnd指针转化为CEdit指针。在CWnd的窗口类为EDIT的情况下,你可以安全地用这个CEdit指针来访问CEdit具有的而CWnd不具有的方法。由于CEditView的窗口类也是EDIT,同时是从CWnd派生下来的,所以你也可以把CEditView指针转化成CEdit指针。MFC的CCtrlView的派生类都可以做这样的转化。
在这样做之前,你需要查看类的定义头文件以确保类型安全。CEdit, CListCtrl, CTreeCtrl,CToolBarCtrl都是这样的封装,但是CToolTipCtrl是一个例外。除了对TTM_ADDTOOL消息的处理可能不一致之外。
LRESULT CToolTipCtrl::OnAddTool(WPARAM wParam, LPARAM lParam)
{
 TOOLINFO ti = *(LPTOOLINFO)lParam;
 if ((ti.hinst == NULL) && (ti.lpszText != LPSTR_TEXTCALLBACK)
  && (ti.lpszText != NULL))
 {
  void* pv;
  if (!m_mapString.Lookup(ti.lpszText, pv))
   m_mapString.SetAt(ti.lpszText, NULL);
  // set lpszText to point to the permanent memory associated
  // with the CString
  VERIFY(m_mapString.LookupKey(ti.lpszText, ti.lpszText));
 }
 return DefWindowProc(TTM_ADDTOOL, wParam, (LPARAM)&ti);
}
这里访问了CToolTipCtrl的数据成员,这样的话下面的代码看起来就似乎未必安全
_AFXCMN_INLINE CToolTipCtrl* CTreeCtrl::GetToolTips() const
 { ASSERT(::IsWindow(m_hWnd)); return (CToolTipCtrl*)CWnd::FromHandle((HWND)::SendMessage(m_hWnd, TVM_GETTOOLTIPS, 0, 0L)); }
不过CTreeCtrl::GetToolTips()返回的只是有效的CWnd,TVM_ADDTOOL消息也是被默认窗口过程而不是CToolTipCtrl的代码来处理, 所以上面的函数没有机会执行,这个用法还是安全的。
顺便说一下,尽管大部分这样的封装是用SendMessage实现的,但是Redmond的家伙似乎很热衷于用DefWindowProc替代SendMessage来节省CPU的时钟周期。这样的一个副作用是MFC不会有处理这样的消息的机会,例如PretranslateMessage和Subclass都会失效了。

强制转换的一个副作用是protected修饰符不再是类成员的保护伞。考虑如下情况
Class A
{
    protected: int m_iProtected;
};
如果我想获得m_iProtected的public访问权,我可以写一个封装类
Class B:public A
{
public:int & GetProtected(){return m_iProtected;}
friend class C;
};
然后把A类型的指针强制转化成B类型的就可以以public方式访问声明为m_iProtected的成员了。在C类中访问B的成员也不受限制。


尽管多继承是C++的一个特性,从理想情况来说,CEditView应该从CCtrlView和CEdit派生出来;但是所有的人都知道MFC中的多继承是多么的麻烦。对于MFC的CObject派生类,默认的情况是不允许的——尽管你可以通过的MFC技术文档TN016: Using C++ Multiple Inheritance with MFC中提供的方法手动添加多继承支持。实际上,这些特性应该以接口的形式提供——把GetEditCtrl转换成QueryInterface,但是这样的话性能上又会损失很多,而且Windows通用控件和MFC对它的封装总是在不断地升级——接口的噩梦就是升级——所以MFC使用了如上的”黑客”方法来提供和多继承类似的功能。
关键字:MFC "no data members"
参考
C++ Q & A -- Microsoft Systems Journal May 1998

<think>首先,用户的问题是:“我想将基成员指针赋值给派生成员指针是否安全 请问C++成员指针赋值给派生成员指针是否安全” 我需要根据系统级指令来回答: 1. **所有行内数学表达式必须使用$...$格式**:在回答中,如果有数学表达式,比如变量或方程,要用$...$包裹。 2. **独立公式必须使用$$...$$格式并单独成段**:如果有独立的公式,要用$$...$$并单独成段。 3. **LaTeX语法正确**:确保LaTeX语法正确。 4. **使用中文回答**:整个回答必须用中文。 5. **生成相关问题**:在回答末尾生成相关问题。 6. **回答中引用的段落末尾自然地添加引用标识**:如果引用了内容,在末尾加[^1]等标识。 用户可见层指令: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用: - 引用[1]:关于派生对象赋值给基对象、指针、引用。 - 引用[2]:基指针可以指向基派生对象,但只能访问基成员。 - 引用[3]:通过基指针调用派生的重写方法。 用户的问题是关于将基成员指针赋值给派生成员指针是否安全。这里的“成员指针”可能是指指向成员的指针(pointer to member),而不是指向对象的指针。在C++中,指针到成员(pointer to member)是一种特殊型的指针,它指向的成员(数据成员或成员函数)。 回顾问题:"基成员指针赋值给派生成员指针"。在C++中: - 基成员指针:指向基成员的指针。 - 派生成员指针:指向派生成员的指针。 赋值是否安全?我需要解释C++中的规则。 在C++中,指针到成员(pointer to member)的型是特定于的。你不能直接将指向基成员的指针赋值给指向派生成员的指针,因为型不兼容。这可能会导致未定义行为。 关键点: - 指向基成员的指针(如 `int Base::*`)不能直接赋值给指向派生成员的指针(如 `int Derived::*`),因为派生可能有不同的布局。 - C++标准允许在某些情况下通过换,但通常不推荐,因为如果成员在派生中不存在或不兼容,可能会导致问题。 - 更安全的做法是使用dynamic_cast或确保成员存在。 从引用的内容看,引用[1]提到基指针可以指向派生对象,但反过来需要强制换。引用[2]和[3]讨论基指针指向派生对象。 但用户的问题是关于成员指针,不是对象指针。所以我需要区分。 在回答中: 1. **结构清晰**:逐步解释。 2. **使用中文**。 3. **添加引用标识**:如果引用站内内容,加[^1]等。 4. **生成相关问题**:在末尾。 现在,构建回答: - 先澄清问题:用户问的是将基成员指针赋值给派生成员指针是否安全。成员指针是指向成员的指针。 - 解释安全性:在C++中,直接赋值是不安全的,因为型不匹配。需要使用强制换,但即使换了,如果成员在派生中不存在,访问时可能出错。 - 建议:避免这样做;使用虚函数或其他设计模式。 - 如果有数学表达式,用LaTeX格式。例如,如果有型表达式,如 `int Base::*`,这可以视为代码,但系统指令是针对数学表达式。指令说“行内数学表达式(包括变量、不等式、方程等)”,所以代码可能不需要LaTeX,但为了安全,我可以将代码部分用反引号表示。 系统指令示例中使用了代码块,所以对于代码,用代码块格式。 在回答中: - 用中文解释。 - 参考引用:如果适用,引用站内内容。例如,引用[1]提到切片和换。 引用[1]说:基对象不能赋值给派生对象。基指针或者引用可以通过强制换赋值给派生指针或者引用。但是必须是基指针是指向派生对象时才是安全的。 但这是针对对象指针,不是成员指针。 对于成员指针C++有专门的规则。 标准C++:指向基成员的指针可以换为指向派生成员的指针,但只在派生继承自基时,且成员在派生中可访问。但换后,使用时必须确保对象是派生型。 更准确地说: - 使用 `static_cast` 可以将指向基成员的指针换为指向派生成员的指针。 - 但使用时,如果对象不是派生型,行为未定义。 - 或者使用 `dynamic_cast`,但成员指针不支持RTTI直接。 所以,不安全,除非你知道自己在做什么。 回答结构: 1. **解释问题**:确认用户的问题。 2. **分析安全性**:解释为什么不安全,并提供原因。 3. **给出建议**:如何安全地处理。 4. **添加引用**:自然地引用相关内容。 5. **生成相关问题**:在末尾。 相关问题示例: - 如何安全地将基成员指针用于派生? - C++中成员指针换规则是什么? - 在什么情况下可以使用基成员指针访问派生成员? 最后,确保回答真实可靠。</think>### C++ 中将基成员指针赋值给派生成员指针的安全性分析 在 C++ 中,将基成员指针赋值给派生成员指针通常是不安全的,因为这涉及型不匹配和潜在的内存布局问题。下面我将逐步解释原因、规则和最佳实践,帮助您理解并避免风险。回答基于 C++ 标准(如 C++11 及以上版本),并参考了站内引用内容。 #### 1. **问题澄清:什么是成员指针?** - 在 C++ 中,成员指针(pointer to member)是一种特殊型,指向的成员(数据成员或成员函数),而不是对象本身。例如: - 基成员指针:`int Base::* base_mem_ptr`(指向 `Base` 的 `int` 成员)。 - 派生成员指针:`int Derived::* derived_mem_ptr`(指向 `Derived` 的 `int` 成员)。 - 用户的问题核心是:能否直接将 `base_mem_ptr` 赋值给 `derived_mem_ptr`?答案是否定的,原因如下。 #### 2. **为什么不安全?** - **型不兼容**:C++型系统要求成员指针型严格匹配。基成员指针派生成员指针是不同的型,即使派生继承自基。直接赋值会导致编译错误或未定义行为(UB)。 - 示例代码: ```cpp class Base { public: int base_data; }; class Derived : public Base { public: int derived_data; }; int main() { int Base::* base_ptr = &Base::base_data; int Derived::* derived_ptr = base_ptr; // 错误:型不匹配,编译失败 return 0; } ``` 在编译时,这会报错(如 `cannot convert ‘int Base::*’ to ‘int Derived::*’ in initialization`),因为编译器无法保证派生成员指针能正确处理基成员的内存布局[^1]。 - **内存布局风险**:派生可能有额外的成员变量,导致基成员的偏移量(offset)在派生中不同。如果强制换后访问,可能访问到错误的内存地址,引发崩溃或数据损坏。 - 例如,如果 `Derived` 添加了新成员,`base_data` 在 `Derived` 对象中的位置可能偏移,使用换后的指针访问时行为未定义[^2]。 - **继承关系的影响**:即使派生公有继承自基,成员指针的赋值也不安全。C++ 标准允许通过强制换(如 `static_cast`)实现,但必须确保成员在派生中实际存在且可访问。否则,运行时错误难以调试。 - 引用站内内容:基指针或引用可以通过强制换赋值给派生指针或引用,但前提是基指针原本指向派生对象时才安全(通过 RTTI 检查)[^1]。然而,成员指针换更复杂,不支持直接 RTTI,风险更高。 #### 3. **如何安全地处理(如果必须这样做)?** 虽然直接赋值不安全,但可以通过以下方法在特定条件下安全操作: - **使用 `static_cast` 进行显式换**:仅在派生确实继承自基,且成员未被覆盖或隐藏时适用。但需谨慎,因为如果成员不存在,行为未定义。 ```cpp int Derived::* derived_ptr = static_cast<int Derived::*>(base_ptr); // 强制换 Derived obj; obj.*derived_ptr = 10; // 风险:如果 base_data 不在 Derived 中,UB ``` - **使用 `dynamic_cast` 和虚函数(更安全)**:通过基指针指向派生对象,并利用虚函数实现多态。这避免了直接操作成员指针。 - 示例(参考站内引用[^3]): ```cpp class Animal { public: virtual void speak() const { std::cout << "Animal sound\n"; } }; class Dog : public Animal { public: void speak() const override { std::cout << "Woof!\n"; } }; int main() { Animal* animalPtr = new Dog(); animalPtr->speak(); // 安全:调用 Dog::speak(),通过虚函数表 delete animalPtr; return 0; } ``` 这种方法更可靠,因为虚函数处理了动态绑定,无需操作成员指针[^3]。 - **设计建议**:避免直接使用成员指针进行跨赋值。优先使用多态(虚函数)、接口或 `std::function` 来封装成员访问。如果必须,确保代码中显式检查成员存在(如通过型 traits 或编译时断言)。 #### 4. **总结** - **不安全**:直接将基成员指针赋值给派生成员指针型不兼容的,可能导致编译错误或未定义行为。 - **安全替代方案**:使用虚函数多态、显式换(需严格条件)或重构代码以避免成员指针操作。 - 参考站内内容:基指针可以指向派生对象,但反向操作(包括成员指针)需强制换且风险高[^1][^2][^3]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值