34

从类返回对象时,什么时候释放内存合适?

例子,

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 没有办法跟踪创建的动物列表,所以我如何更改此类代码的逻辑以删除创建的对象。

4

9 回答 9

36

根据您的使用情况,您可以在此处使用几个选项:

  1. 每次创建动物时都复制一份:

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

    优点:

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

    缺点:

    • 它需要Animal一个行为良好的复制构造函数。
    • Animal如果大而复杂,它可能涉及大量复制,尽管返回值优化可以在许多情况下缓解这种情况。
    • 如果您计划返回派生自的子类,则不起作用,Animal因为它们将被分成plain 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非常适合在有限的时间内需要一堆s 并计划一次性释放它们的情况。
    • 轻松适应自定义内存池并Animal在单个delete.
    • 适用于对象层次结构(无对象切片)。
    • 不必复制大对象没有问题。
    • 无需Animal定义复制构造函数。
    • 不需要外部库。

    缺点:

    • 上面写的实现不是线程安全的
    • 需要额外的支持代码
    • 不如前两个方案清晰
    • 不明显的是,当 AnimalLister 超出范围时,它会带走 Animals。你不能像挂在 AnimalLister 上那样更长时间地挂在 Animals 上。
于 2009-01-06T21:29:31.040 回答
24

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

共享指针为您处理引用跟踪,并在没有引用时自动删除对象。

于 2008-10-15T11:34:21.840 回答
8

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

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

如果你能够使用 TR1 或 Boost,你也可以使用 shared_ptr<>。

于 2008-10-15T11:34:33.010 回答
8

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

您可以在 AnimalLister 本身中存储指向每个分配的 Animals 的指针,并让它清理它们。

但是,您确实有几个指向位于 main() 中的 Animals 的指针,它们将引用已删除的内存。

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

于 2008-10-15T11:41:49.330 回答
5
  1. shared_ptr (效果很好),
  2. 返回一个简单的指针并告诉你的类的用户它现在是他们的动物,他们有责任在完成后删除它,
  3. 实现一个 'freeAnimal(Animal*)' 方法,这使得很明显需要删除动物指针。

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

所以:

class AnimalLister 
{
Animal getAnimal() { Animal a; return a; }; // uses fast Return Value Optimisation
};

Animal myownanimal = AnimalLister.getAnimal(); // copy ctors into your Animal object

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

于 2008-10-15T12:07:38.387 回答
3

Scott Meyers 的彻底讨论中,他得出结论,使用 shared_ptr 或 auto_ptr 是最好的。

于 2009-06-11T11:44:04.997 回答
2

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

  • 创建对象时,立即给它一个参考值 1
  • 当任何人得到指针的副本时,他们 AddRef()
  • 当任何人放弃他们的指针副本时,他们 Release()

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

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

在我的开发过程中,我还没有给 shared_ptr 太多的机会,所以这可以完美地满足你的目的。

于 2008-10-15T11:42:03.447 回答
2

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

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

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

于 2008-10-15T12:36:50.013 回答
0

I really like Josh's answer, but I thought I might throw in another pattern because it hasn't been listed yet. The idea is just force the client code to deal with keeping track of the animals.

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;
  }
}
于 2009-02-07T22:12:31.133 回答