返回指向对象的参数指针
Posted
技术标签:
【中文标题】返回指向对象的参数指针【英文标题】:Returning an argument pointer to an object 【发布时间】:2010-09-27 16:02:05 【问题描述】:在 Windows 的 C++ 中,我有一些对象工厂,它应该通过将指向对象的指针传递给 Create 函数并返回创建的对象来创建一系列 Info 对象。
void CreateInfoObject(AbstractInfo** info); // The creation function
AbstractInfo 是一个基类,我们从中派生出许多类型的 Info 对象。
我想我现在可以如下创建一个 Info 对象:
MyInfoObject* InfoObj = NULL; // derived from AbstractInfo object
InfoFactory fc;
fc.CreateInfoObject(&InfoObj); // Now I want to get my initialized pointer back
但它说它不能进行演员表...怎么了?
错误: 无法从 MyInfoObject**_W64 转换为 AbstractInfo**
编辑:第一个答案提到界面很糟糕,看不到谁在分配等等......我该如何改进?
【问题讨论】:
【参考方案1】:让我们考虑一下CreateInfoObject
的可能实现:
void InfoFactory::CreateInfoObject(AbstractInfo** info)
*info = new SuperInfo;
现在,SuperInfo
和 MyInfoObject
没有任何共同点吧?
这就是为什么通常禁止以下行为:
struct Base ;
struct D1: Base ;
struct D2: Base ;
int main(int argc, char* argv[])
Base** base = nullptr;
D1* d = nullptr;
base = d;
因为它允许D1
指向不相关的东西。
有几种解决方案:
// 1. Simple
AbstractInfo* info = nullptr;
fc.CreateInfoObject(info);
// 2. Better interface
std::unique_ptr<AbstractInfo> info = fc.CreateInfoObject();
然后,如果您确定自己确实拥有MyInfoObject
,则可以使用:
MyInfoObject* myInfo = static_cast<MyInfoObject*>(info);
或者如果您不确定:
MyInfoObject* myInfo = dynamic_cast<MyInfoObject*>(info);
如果info
没有指向MyInfoObject
的实例(或派生的),则将myInfo
设置为nullptr
。
但请记住,您的界面真的很糟糕。它非常 C-ish,不清楚是否实际分配了内存......如果是,谁负责处理它。
编辑:
在good C++ 风格中,我们使用 RAII 来表示所有权并确保清理。 RAII 是众所周知的,虽然不是很有指示性,但我自己更喜欢新的 SBRM(范围绑定资源管理)。
这个想法是,而不是使用一个裸指针,它不表明任何关于所有权的事情(即你必须在它上面调用 delete 吗?)你应该使用一个智能指针,例如unique_ptr
.
也可以利用方法的返回参数,避免两步初始化过程(先创建指针,再使其指向一个对象)。这是一个简洁的例子:
typedef std::unique_ptr<AbstractInfo> AbstractInfoPtr;
// Note: if you know it returns a MyInfoObject
// you might as well return std::unique_ptr<MyInfoObject>
AbstractInfoPtr InfoFactory::CreateInfoObject()
return AbstractInfoPtr(new MyInfoObject());
// Usage:
int main(int argc, char* argv[])
InfoFactory factory;
AbstractInfoPtr info = factory.CreateInfoObject();
// do something
// info goes out of scope, calling `delete` on its pointee
这里没有关于所有权的歧义。
另外,请注意如何更好地理解您的问题:
std::unique_ptr<MyInfoObject> info = factory.CreateInfoObject();
无法编译,因为不使用 static_cast
或 dynamic_cast
就无法将 AbstractInfo*
转换为 MyInfoObject*
。
【讨论】:
@Tony:我已经编辑了我的答案并提出了替代界面的建议。 非常感谢您的解释。现在我仍然可以从检索到的 AbstractInfoPtr 向下转换为派生对象吗?这是个好主意吗? @Tony:是和不是。如果您的设计不需要向下转换,那就更好了,但是如果您被AbstractInfoPtr
卡住并且需要更多派生的对象,那么继续向下转换。没有设施(你不能直接使用static_cast
)所以你必须手动完成:1.获取指针并将其转换,2.如果成功将其分配给另一个unique_ptr
,3.从第一个释放它.如果是static_cast
,您可以使用快捷方式 2。它给出:unique_ptr<MyInfoObject> myinfo ( static_cast<MyInfoObject*>(info.release()) );
。对于dynamic_cast
,这可能会失败,因此之前的测试。【参考方案2】:
因为CreateInfoObject()
需要一个指向AbstractInfo
的指针,所以该函数有可能返回一个AbstractInfo
的实例,该实例不是 MyInfoObject
。因此,您最终可能会得到一个指向 MyInfoObject
的指针,实际上 指向 DifferentInfoObject
。
将MyInfoObject *InfoObj
更改为AbstractInfo *InfoObj
,它应该可以工作。除了dynamic_cast<>
之外,不要放弃转换,因为您不确定CreateInfoObject()
返回该子类的实例。
【讨论】:
【参考方案3】:编译器告诉你出了什么问题。当 T 和 U 彼此无关时,您不能将 T* 类型的指针转换为 U* 类型的指针。这里,T=MyInfoObject*、U=AbstractInfo* 是两种不同的指针类型,不共享任何继承关系。
【讨论】:
问题不在于指针。问题是CreateInfoObject()
可以返回一个AbstractInfo
的实例,它不是也是MyInfoObject
的一个实例,而调用代码无法确保不会发生这种情况。
为了强化这个答案:即使类 MyInfoObject 和 AbstractInfo 具有继承关系,指针类型 MyInfoObject* 和 AbstractInfo* 却没有。
@Jonathan:调用代码无论如何都是无效的。它是无效的,因为 T 和 U 与引用无关。编译器不允许你做这样的事情,除非你明确地转换指针,在这种情况下你可能会遇到你所指的问题。
@Jonathan:sellibitze 是正确的;即使不涉及继承,也会发生此错误。例如,void f(void **vp); int *i; f(&i);
也是非法的,尽管int*
和void*
之间存在转换。
我只想再提一件事。将 Derived* 类型的指针转换为 Base* 可能涉及指针调整。这就是为什么 Derived* 和 Base* 不与引用相关的另一个原因,即使 Derived 和 Base 与引用相关。【参考方案4】:
指向指针的指针不如指向对象的指针灵活。编译器将严格执行类型而不考虑继承树。
修复此代码最安全的方法是使用双重赋值:
MyInfoObject* InfoObj = NULL; // derived from AbstractInfo object
AbstractInfo* temp = NULL;
InfoFactory fc;
fc.CreateInfoObject(&temp);
InfoObj = dynamic_cast<MyInfoObject*>(temp);
【讨论】:
即使他在不使用任何指针的情况下返回一个对象(例如,使用方法AbstractInfo InfoFactory::CreateInfoObject();
)也会存在问题。他试图将一个抽象类转换为一个具体的子类,这会产生一个坏-形成的程序,不会编译。
@Jonathan,这个问题正是我在答案中使用dynamic_cast
的原因。如果返回的对象实际上不是 MyInfoObject
,则转换将失败。【参考方案5】:
考虑一下CreateInfoObject
中发生的情况。
假设AbstractInfo
的另一个子类,调用Foo
。
在CreateInfoObject
中,我们创建一个新的Foo
并将其分配给*info
。 (允许向上转换)。
但是在外部,我们现在在 MyInfoObject**
内部有 Foo
,这是错误的。
【讨论】:
以上是关于返回指向对象的参数指针的主要内容,如果未能解决你的问题,请参考以下文章
为啥 const QString& 参数返回错误的 const char* 指向数据的指针