【问题标题】:C++ : about memory managementC++:关于内存管理
【发布时间】:2012-01-24 11:02:06
【问题描述】:

我对 C++ 有点陌生,到目前为止,我一直在使用 Obj-C 和 Java 进行编程。

说,我有课:

class Person {

private:
   Wife *current_wife;
   //.....
};

所以 obv 我需要实现一个 setter 方法来更改 Wife 实例变量。

像这样:

Person::SetCurrentWife (Wife *new_wife) {

    current_wife = new_wife;
}

那将是一个浅拷贝。

所以在主循环的某个地方或我称之为:

Person *some_person = new Person();
...
Wife *wife = new Wife ();
some_person->SetCurrentWife(wife);

所以我很困惑:这里会不会有内存泄漏?我应该在此处还是在 Person 的析构函数中删除妻子对象?在 Obj-C 中,我会释放当前的妻子,然后向上面的妻子对象发送保留消息,但是在 C++ 中执行 setter 方法的正确方法是什么?

【问题讨论】:

  • 由于您是 C++ 新手,请注意以下建议:永远不要使用 newdelete 或指针。作为一个例外,您可以在智能指针的构造函数中使用new,但前提是您确定确实需要动态存储。
  • Kerrek 忘了推荐a good introductory C++ book :)
  • 在OO中你不需要setter,你需要让对象做某事的函数(离婚,再婚)。 Setter 可能很方便,但它们不是 obv。
  • 您必须问自己的问题是:“Person 是否拥有他的Wife?”和“当Person 的生命周期结束时,他的Wife 是否应该被自动销毁?”。
  • 下意识地呼唤“智能指针”的任何答案的问题在于他们没有抓住重点——设计过程中的第一个问题不应该是如何实现动态存储,但是否需要动态存储。这是我认为 Java 和任何“现代 OO”负责的最大罪行之一:它完全削弱了人们思考其数据的底层 逻辑结构 的能力。 首先您考虑生命周期,然后才继续实现所需的语义。

标签: c++ memory memory-management memory-leaks


【解决方案1】:

这取决于你想要做什么。首先,正如 Kerrek SB 评论说,如果可以使用值语义,您就不想使用指针 应用:如果Wife 是可复制和可分配的,几乎没有理由 动态分配它。然而,在这种情况下,我猜 Wife 派生自 Person(尽管可能是 Person 的装饰器 会更合适,因为给定的Person isA Wife 可以随时间变化),甚至可能有派生的类型 来自Wife(而Person::current_wife 可能想要持有其中之一 这些),事实上,Wife 有身份;你不想要的副本 到处都是同一个妻子。

如果是这种情况,那么您确实必须为 其他类与Wife 的交互。通常,使用寿命 Wife 不会依赖于持有它的 Person(尽管如果它是 装饰器,它可能),所以Person 应该只持有一个指向它的指针,如 你已经完成了。最有可能的是,Wife 对象将具有各种功能 隐式或显式地控制它的生命周期:你 可能有类似的东西:

void Wife::die()
{
    //  ...
    delete this;
}

例如。然而,在这种情况下,与Wife 结婚的人将 必须被告知,这样current_wife 就不会指向死亡 伴侣。通常,观察者模式的一些变体可用于 这。 (请注意,您在 Java 中遇到了完全相同的问题;您没有 想要Person::current_wife 指向一个死的Wife。所以你还是 需要一个Wife::die() 函数和观察者模式来通知 配偶。)

在这种情况下(根据我的经验,这种情况占绝大多数 在 C++ 中动态分配的对象),关于唯一的区别 C++ 和 Java 之间的区别在于 C++ 有一种特殊的语法来调用 “破坏者”;在 Java 中,您使用通常的函数调用 语法,你可以给函数起任何你想要的名字(虽然 dispose 似乎被广泛使用)。特殊语法允许编译器 生成额外的代码来释放内存,但所有其他的 与对象生命周期结束相关的活动仍然必须 被编程(在析构函数中,在 C++ 中——尽管在这种情况下,它 将其中一些直接放在Wife::die 中可能是有意义的 函数)。

【讨论】:

    【解决方案2】:

    智能指针可以帮助您

    using boost::shared_ptr; // or std::shared_ptr, or std::tr1::shared_ptr or something like this
    class Person { 
    
    private: 
       shared_ptr<Wife> current_wife; 
       //..... 
    }; 
    
    Person::SetCurrentWife (shared_ptr<Wife> new_wife) { 
    
        current_wife = new_wife; 
    } 
    

    现在你根本不应该删除任何妻子。

    shared_ptr<Person> some_person ( new Person );
    ...  
    shared_ptr<Wife> wife ( new Wife );  
    some_person->SetCurrentWife(wife);  
    

    【讨论】:

    • 这听起来像是灾难的秘诀。只有极少数情况下shared_ptr 有效,我怀疑这不是其中之一。有两个原因:首先,很可能Wife 也知道她是谁的妻子,所以你有一个周期(而且你需要一个周期,因为如果 Wife 发生了什么事,必须通知当前配偶)。其次,Wife 的生命周期当然不取决于配偶的生命周期——如果 Wife 死了,她不应该仅仅因为配偶恰好保持指向她的指针而活着。
    • @JamesKanze 这就是为什么会有boost::weak_ptr 我猜。它允许中断循环并安全地通知死对象。
    • @ChristophHeindl 那么哪些指针应该是弱的,哪些不是? boost::weak_ptr 是一个 hack,一般不可用。
    • @JamesKanze 这取决于您的应用程序试图完成什么。一种方式:维持weak_ptr 用于人际关系,并单独持有一个容器shared_ptr 的所有活着的人。毕竟,如果您在处理人际关系时需要确保“活力”,您可以随时将weak_ptr 临时升级为shared_ptr
    • @ChristophHeindl 当然可以。无论如何,有点;例如,配偶可能想立即知道Wife 何时死亡。额外的向量似乎无缘无故地增加了复杂性。看起来设计已经被扭曲了,所以你可以说你正在使用智能指针。
    【解决方案3】:

    您应该使用智能指针。

    如果您使用常规指针,请谨慎操作!

    您应该在析构函数和 set 方法上都使用 delete 旧的 current_wife 成员。设置一个新的妻子会导致旧的内存泄漏,因为指向分配内存的指针丢失(除非你在类外管理内存 - 见下文)。

    但即使这样做,您也需要确保班级以外的任何人都不能删除该成员。您必须决定是否将内存管理留给班级或分派到班级外部,并坚持下去。

    【讨论】:

    • -1 因为这只是个坏建议。如果智能指针实现了所需的生命周期语义,我会感到非常惊讶——你真的不相信我妻子不可能在我之前死去(或者我的死应该触发她的死),是吗?跨度>
    • @JamesKanze 那为什么会有问题呢?使用智能指针会限制您事先删除妻子对象,还是在人死后删除它?
    • 它取决于智能指针,但如果您删除由任何标准智能指针管理的内容,您将处于未定义的行为状态。如果您使用shared_ptr,尤其是,对象的生命周期不在您的掌控之中。
    • @James:这可能是特定于文化的。拥有一个 Person 类然后是一个 Wife 类通常是很奇怪的。在某些文化中,人与其他人结婚。
    • @UncleBens 整个结构有点可疑:如果Wife 源自Person,那么有些人从出生到死都是妻子。如果Wife 不是源自Person,那么人们会想知道一个妻子不是人的社会。 (我想我含糊地提到可能使用装饰器模式,其中Wife 是装饰的Person。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-26
    • 1970-01-01
    • 2012-08-08
    • 1970-01-01
    • 2010-09-06
    相关资源
    最近更新 更多