不使用悬空指针在C++中返回抽象数据类型

5

你好,

我来自C#背景,对C++的经验不是很丰富。为了编写干净的代码,我尝试将实现和接口分离,并在可能的情况下使用继承。但当我尝试将典型的C#概念应用到C++上时,遇到了一个问题,一直无法解决。我认为对于有经验的C++程序员来说,这可能很简单,但对我来说已经困扰了很长时间。

首先,我声明了一个基类(目前它没有任何逻辑,但以后会有)

class PropertyBase : public IProperty
{
};

然后我为属性定义一个接口

class IProperty
{
public:
    virtual ~IProperty() {};
    virtual PropertyBase    correct(const ICorrector &corrector) = 0;
    virtual PropertyBase    joinWith(const PropertyBase &partner, const IRecombinator &recombinator) = 0;
};

这就是问题所在:编译器会返回错误,指出不允许声明一个返回抽象类的函数。当然,我不想返回类型为PropertyBase的对象。我希望声明其他继承自PropertyBase的类,它们可以返回自己的实例。
现在,我已经阅读到一个可能的解决方法是修改IProperty,像这样返回指针:
class IProperty
{
public:
    virtual ~IProperty() {};
    virtual PropertyBase*   correct(const ICorrector &corrector) = 0;
    virtual PropertyBase*   joinWith(const PropertyBase &partner, const IRecombinator &recombinator) = 0;
};

然而,如果可能的话,我希望避免这种情况以防止内存泄漏。如果有更好的处理此问题的方法,那就太好了。

非常感谢。

5个回答

5
如果你担心内存泄漏,可以使用智能指针。这样做的额外好处是在返回对象的所有权方面具有自我记录的功能。
class IProperty
{
public:
    virtual ~IProperty() {};
    virtual std::unique_ptr<PropertyBase> correct(const ICorrector &) = 0;
    virtual std::unique_ptr<PropertyBase> joinWith(const PropertyBase &,
                                                   const IRecombinator &) = 0;
};

在你的客户端代码中:
std::unique_ptr<PropertyBase> pb(property.correct(corrector));
// use pb and forget about it; smart pointers do their own cleanup

或者,如果您想在对象上进行引用计数:

std::shared_ptr<PropertyBase> pb(property.correct(corrector));

请参阅 MSDN 文档,了解 unique_ptrshared_ptr


2
这可能不是您想要的答案,但我认为您对C++中指针和值有点困惑。
如果您想要适当的ad-hoc多态性,必须在C++中返回指针或引用。在这种情况下,编译器发出了一个错误,因为基类是抽象的。如果实例化抽象类是可能的,它将具有“空洞”。
拇指规则是:每当您有一个类层次结构时,永远不要按值返回此类类型的对象。假设您有class Base { int x; }class Derived : public Base { int y; }。如果您这样做:
Base Function() { Derived d; return d; }
...
Base b = Function();

然后,b将不是类Derived的值“隐藏在”Base后面。值b将是Base。编译器将“切掉”DerivedBase之间的差异,并将其放入b中。
在C++中,您将需要使用指针或引用来促进特定情况下的多态性。在C#中,引用与C++中的指针基本相同,唯一的区别是您不必在C#中释放对象,因为垃圾回收器会为您处理此问题。

0

这个问题有一个非常简单的解决方案。使用指针或引用作为返回值,但是在指针中不要返回所有权。

例如:

class A : public Base
{
public:
   Base *correct(const I &c) 
     { p2 = do_something(c); return &p2; }
   ...
private:
   A2 p2;
};

这个方法的关键在于将p2存储在类内部,从未将对象的所有权传递到外部。接口中的函数不会创建新对象,而是返回现有对象,根据函数参数配置为正确状态。这是unique_ptr和shared_ptr解决方案的良好替代品,后者依赖于堆分配、创建新对象并将其传递。
现在,这个技巧的好处在于你需要在类的数据成员中列出你想从correct()函数返回的所有可能类型。例如,如果有时你会返回不同的类型,它会像这样:
class B : public Base
{
public:
   Base *correct(const I &c) {
      switch(c.get_bool()) {
         case false: p3 = do_something_else(c); return &p3;
         case true: p4 = do_something(c); return &p4;
      };
   }
 private:
    B3 p3;
    B4 p4;
};

但是将您的对象p3和p4放置在当前B对象内部将完全解决此问题。


0

返回对象指针并没有什么问题。如果你担心内存泄漏,而你应该担心,解决方案是使用智能指针来存储返回的指针。其中最灵活的是boostshared_ptr,或即将到来的C++0x标准。


1
如果函数返回指针保证返回一个(指向)新对象,我建议使用std::unique_ptrshared_ptr可以从unique_ptr构造。 - Fred Foo

0
更普遍地说,如果你要在C++中进行任何大量的工作,那么熟悉指针和内存管理是非常重要的。为了深入探讨C++的这些棘手方面,我强烈推荐Scott Meyers的Effective C++系列书籍和Herb Sutter的Exceptional C++系列书籍。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接