在C ++中返回对象

时间:2008-10-15 11:29:35

标签: c++ memory-management factory

从类中返回对象时,何时是释放内存的正确时间?

实施例,

class AnimalLister 
{
  public:
  Animal* getNewAnimal() 
  {
    Animal* animal1 = new Animal();
    return animal1;
  }
}

如果我创建一个Animal Lister的实例并从中获取Animal引用,那么我应该删除它的位置?

int main() {
  AnimalLister al;
  Animal *a1, *a2;
  a1 = al.getNewAnimal();
  a2 = al.getNewAnimal();
}

问题在于AnimalLister没有办法跟踪动物创建的列表,所以如何更改此类代码的逻辑以便删除创建的对象。

9 个答案:

答案 0 :(得分:36)

根据您的使用情况,您可以使用以下几种选项:

  1. 每次创建动物时都要制作副本:

    class AnimalLister 
    {
    public:
      Animal getNewAnimal() 
      {
        return Animal();
      }
    };
    
    int main() {
      AnimalLister al;
      Animal a1 = al.getNewAnimal();
      Animal a2 = al.getNewAnimal();
    }
    

    优点:

    • 易于理解。
    • 不需要额外的库或支持代码。

    缺点:

    • 要求Animal拥有一个表现良好的复制构造函数。
    • 如果Animal大而复杂,可能会涉及大量复制,尽管return value optimization可以在许多情况下缓解这种情况。
    • 如果您打算将从Animal派生的子类返回到sliced到普通Animal,则会失效,从而丢失子中的所有额外数据类。
  2. 返回shared_ptr<Animal>

    class AnimalLister 
    {
    public:
      shared_ptr<Animal> getNewAnimal() 
      {
        return new Animal();
      }
    };
    
    int main() {
      AnimalLister al;
      shared_ptr<Animal> a1 = al.getNewAnimal();
      shared_ptr<Animal> a2 = al.getNewAnimal();
    }
    

    优点:

    • 使用对象层次结构(无对象切片)。
    • 不必复制大型对象。
    • 无需Animal定义复制构造函数。

    缺点:

    • 需要Boost或TR1库,或其他智能指针实现。
  3. 跟踪Animal

    中的所有AnimalLister分配
    class AnimalLister 
    {
      vector<Animal *> Animals;
    
    public:
      Animal *getNewAnimal() 
      {
        Animals.push_back(NULL);
        Animals.back() = new Animal();
        return Animals.back();
      }
    
      ~AnimalLister()
      {
         for(vector<Animal *>::iterator iAnimal = Animals.begin(); iAnimal != Animals.end(); ++iAnimal)
            delete *iAnimal;
      }
    };
    
    int main() {
      AnimalLister al;
      Animal *a1 = al.getNewAnimal();
      Animal *a2 = al.getNewAnimal();
    } // All the animals get deleted when al goes out of scope.
    

    优点:

    • 非常适合在有限时间内需要一堆Animal的情况,并计划一次性释放它们。
    • 轻松适应自定义内存池并在单个Animal中释放所有delete
    • 使用对象层次结构(无对象切片)。
    • 不必复制大型对象。
    • 无需Animal定义复制构造函数。
    • 不需要外部库。

    缺点:

    • 上面所述的实现不是线程安全的
    • 需要额外的支持代码
    • 不如前两个方案明确
    • 不明显的是,当AnimalLister超出范围时,它会带动动物。除了挂在AnimalLister上之外,你不能再挂在动物身上了。

答案 1 :(得分:24)

我建议返回std::tr1::shared_ptr(或boost::shared_ptr,如果你的C ++实现没有TR1)而不是原始指针。因此,不要使用Animal*,而是使用std::tr1::shared_ptr<Animal>

共享指针为您处理引用跟踪,如果没有任何引用,则自动删除该对象。

答案 2 :(得分:8)

最简单的方法是返回智能指针而不是常规指针。 例如:

std::auto_ptr< Animal> getNewAnimal() 
{
  std::auto_ptr< Animal > animal1( new Animal() );
  return animal1;
}

如果您能够使用TR1或Boost,您还可以使用shared_ptr&lt;&gt;。

答案 3 :(得分:8)

指针和分配内存的经典问题。这是责任 - 谁负责清理AnimalLister对象分配的内存。

您可以在AnimalLister中存储指向每个已分配动物的指针,并让它清理干净。

但是,你确实有几个指向动物坐在main()中的指针,这些指针会引用被删除的内存。

我认为引用计数解决方案比滚动自己的解决方案更有效的原因之一。

答案 4 :(得分:5)

  1. shared_ptr(效果很好),
  2. 返回一个简单的指针,告诉你的班级用户现在是他们的动物,他们有责任在完成后删除它,
  3. 实现'freeAnimal(Animal *)'方法,显然需要删除动物指针。

  4. 另一种方法是直接返回动物对象,没有指针,没有新的调用。复制构造函数将确保调用者获取他们可以存储在堆或堆栈上的动物对象,或者根据需要复制到容器中。

  5. 所以:

    class AnimalLister 
    {
    Animal getAnimal() { Animal a; return a; }; // uses fast Return Value Optimisation
    };
    
    Animal myownanimal = AnimalLister.getAnimal(); // copy ctors into your Animal object
    

    RVO意味着返回对象而不是指针实际上更快(因为编译器不创建新对象并将其复制到调用者的对象中,但直接使用调用者的对象)。

答案 5 :(得分:3)

thorough discussion by Scott Meyers中,他得出结论,使用shared_ptr或auto_ptr是最好的。

答案 6 :(得分:2)

或者您可以遵循COM-ish方法,并应用简单的引用计数。

  • 创建对象时,立即为其指定参考值
  • 当有人获得指针的副本时,他们就是AddRef()
  • 当有人放弃指针副本时,他们会释放()

如果引用计数达到0,则对象将自行删除。

它最终是shared_ptr在幕后所做的事情,但它让你可以更好地控制最新情况,并且根据我的经验更容易调试。 (它也非常跨平台)。

我还没有给我的开发提供过多的机会,所以这可能完全符合你的目的。

答案 7 :(得分:2)

释放对象占用的内存的时间是您不再需要该特定对象的时间。在您的特定情况下,AnimalLister类的用户请求指向Animal类的新分配对象的指针。所以,当他确实需要指针/对象时,他就是那个负责释放内存的人。

AnimalLister lister;
Animal* a = lister.getNewAnimal();
a->sayMeow();
delete a;

在我看来,在这种情况下没有必要过度设计任何东西。 AnimalLister只是一个创建新Animal对象的工厂,就是这样。

答案 8 :(得分:0)

我真的很喜欢Josh的回答,但我想我可能会投入另一种模式,因为它尚未列出。这个想法只是迫使客户代码处理跟踪动物的问题。

class Animal
{
...
private:
  //only let the lister create or delete animals.
  Animal() { ... }
  ~Animal() { ... } 
friend class AnimalLister;
...
}

class AnimalLister 
{
  static s_count = 0;

public:
  ~AnimalLister() { ASSERT(s_count == 0); } //warn if all animals didn't get cleaned up

  Animal* NewAnimal() 
  {
    ++count;
    return new Animal();
  }

  void FreeAnimal(Animal* a)
  {
    delete a;
    --s_count;
  }
}