【问题标题】:Returning Objects in C++在 C++ 中返回对象
【发布时间】:2010-09-17 07:00:27
【问题描述】:

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

例子,

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

【问题讨论】:

  • 我想你的意思是“动物 *a1, *a2;”不是“动物 a1, a2;”

标签: c++ memory-management factory


【解决方案1】:

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

  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. 跟踪AnimalLister 中的所有Animal 分配

    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.
    

    优点:

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

    缺点:

    • 上面写的实现不是线程安全的
    • 需要额外的支持代码
    • 不如前两种方案清晰
    • 不明显的是,当 AnimalLister 超出范围时,它会带走 Animals。与 AnimalLister 相比,您不能再继续使用 Animals。

【讨论】:

  • 返回值优化的链接失效了。
【解决方案2】:

我建议返回 std::tr1::shared_ptr(或 boost::shared_ptr,如果您的 C++ 实现没有 TR1)而不是原始指针。所以,不要使用Animal*,而是使用std::tr1::shared_ptr&lt;Animal&gt;

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

【讨论】:

  • C++0x 标准将有一个 unique_ptr,它没有 shared_ptr 的开销,仍然可以满足您的需要。
  • std::auto_ptr 足以应付这种情况。
  • @Martin:我建议不要使用 auto_ptr,因为有两个版本具有根本不同的行为,它们不适用于 STL 容器等。在这种情况下它们没问题,但我发现 std ::tr1 / boost ptrs 不那么模棱两可了。
【解决方案3】:

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

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

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

【讨论】:

  • 如何实现跟踪能力?
  • auto_ptr 是标准 C++ 吗?这看起来很容易,所以 auto_ptr 处理垃圾收集?
  • auto_ptr 是违反直觉的,所以除非您确切知道它的作用,否则不要使用它。 shared_ptr 建议正常使用。
  • auto_ptr 不是引用计数——如果你开始复制构造它就很难做到正确——只用于本地范围删除——从不用于返回或传递参数。
  • 我同意 Chris,shared_ptr 更直观。但是 shared_ptr 还不是标准的。 Lou,你为什么认为 auto_ptr 不应该用于返回和传递参数?请争辩。
【解决方案4】:

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

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

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

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

【讨论】:

  • 我完全同意责任的想法!智能指针的奢华让我们忘记了思考。
  • 啊……权力越大,责任越大。
【解决方案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 意味着返回对象而不是指针实际上更快(因为编译器不会创建新对象并将其复制到调用者的对象中,而是直接使用调用者的对象)。

【讨论】:

  • 按值返回的问题是你不能返回一个子类——它会被分割成Animal类
  • 但是第二个和第三个提议的解决方案非常有用:明确释放动物的责任。
【解决方案6】:

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

【讨论】:

    【解决方案7】:

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

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

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

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

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

    【讨论】:

    • 我认为您的意思是 shared_ptr,而不是 auto_ptr。 auto_ptr 不做引用计数,它做所有权转移语义,这不是大多数人想要的。 :-)
    • 糟糕 ;-)。正如我所说,还没有真正使用它(证明非常尴尬!)
    【解决方案8】:

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

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

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

    【讨论】:

      【解决方案9】:

      我真的很喜欢 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;
        }
      }
      

      【讨论】:

        猜你喜欢
        • 2012-10-24
        • 2019-12-09
        • 2011-11-17
        • 2010-11-24
        • 2011-03-21
        • 2012-07-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多