【问题标题】: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**

编辑:第一个答案提到界面很糟糕,看不到谁在分配等等......我该如何改进?

【问题讨论】:

    标签: c++ pointers casting


    【解决方案1】:

    让我们考虑一下CreateInfoObject 的可能实现:

    void InfoFactory::CreateInfoObject(AbstractInfo** info)
    {
      *info = new SuperInfo;
    }
    

    现在,SuperInfoMyInfoObject 没有任何共同点吧?

    这就是为什么通常禁止以下行为:

    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_castdynamic_cast 就无法将 AbstractInfo* 转换为 MyInfoObject*

    【讨论】:

    • @Tony:我已经编辑了我的答案并提出了替代界面的建议。
    • 非常感谢您的解释。现在我仍然可以从检索到的 AbstractInfoPtr 向下转换为派生对象吗?这是个好主意吗?
    • @Tony:是和不是。如果您的设计不需要向下转换,那就更好了,但是如果您被AbstractInfoPtr 卡住并且需要更多派生的对象,那么继续向下转换。没有设施(你不能直接使用static_cast)所以你必须手动完成:1.获取指针并将其转换,2.如果成功将其分配给另一个unique_ptr,3.从第一个释放它.如果是static_cast,您可以使用快捷方式 2。它给出:unique_ptr&lt;MyInfoObject&gt; myinfo ( static_cast&lt;MyInfoObject*&gt;(info.release()) );。对于dynamic_cast,这可能会失败,因此之前的测试。
    【解决方案2】:

    因为CreateInfoObject() 需要一个指向AbstractInfo 的指针,所以该函数有可能返回一个AbstractInfo 的实例,该实例不是 MyInfoObject。因此,您最终可能会得到一个指向 MyInfoObject 的指针,实际上 指向 DifferentInfoObject

    MyInfoObject *InfoObj 更改为AbstractInfo *InfoObj,它应该可以工作。除了dynamic_cast&lt;&gt; 之外,不要放弃转换,因为您不确定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(&amp;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,这是错误的。

      【讨论】:

        猜你喜欢
        • 2017-12-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-15
        • 2013-04-10
        • 1970-01-01
        • 1970-01-01
        • 2020-11-01
        相关资源
        最近更新 更多