1

我有一个关于在 C++ 中正确删除/释放内存的问题。

假设我有以下...(假设 A、B、C、D 是类。B 和 C 有实例变量A* a。D 有两个实例变量,B* b并且C* c

A* a = new A();
B* b = new B(a);
C* c = new C(a);
D* d = new D(b, c);

B 和 C 的析构函数:

B::~B() { delete a; }
C::~C() { delete a; }

D的析构函数:

D::~D() { delete b; delete c; }

现在当我打电话

delete d;

我收到“访问冲突读取位置 0xfeeefeee”(我在 Visual Studio 2010 中)。我认为这是因为

-D的析构函数试图“删除”相同的内存 ( a) 两次,a但已被释放。

-我有两个指针(一个 inB和另一个 in C)都指向同一个地址(of a),并且当 D 的析构函数删除b(依次调用delete a)时,该内存现在设置为已释放。

- 现在当D' 的析构函数 deletes时cc尝试delete a自行调用失败,因为a已经被释放。

我对 C++ 比较陌生,但对编程并不陌生。我查了一下,发现智能指针(如 shared_ptr)可以解决这个问题,但在这种情况下,最佳实践是什么?我应该只创建两个单独的A对象吗?

4

5 回答 5

5

如果您的设计计划与和共享相同的内容,则应将指针封装在共享指针中。abc

但是,仅在某些情况下才需要使用指针。在大多数情况下,引用也可以完成这项工作。在您的情况下,如果您定义这四个实例的上下文也应该限制它们的生命周期,只需将它们定义为值:

A a;
B b(a);
C c(a);
D d(b, c);

要通过引用传递对象,请在构造函数和私有成员中使用引用类型。并且不要调用delete它们的析构函数。

class B {
    A & m_a;
public:
    B (A &a) : m_a(a) {
    }
};

引用和指针的一个非常重要的区别(比您一开始可能想象的要多)是您不能更改引用(只能更改引用的值)。此外(这是这一事实的暗示),必须在定义变量时对其进行赋值。这就是为什么你必须:m_a(a)在构造函数中使用奇怪的初始化语法()而不是在它的主体中赋值(m_a = a不起作用)。

此外,永远不要删除在构造函数中传递的析构函数中的实例,除非您知道自己在做什么,这意味着您知道三法则。但是智能指针使这个过时了。

于 2013-10-26T23:34:24.450 回答
4

这里的问题在于决定谁拥有该A对象。似乎两者都B认为C自己是所有者,因为他们delete指向a. 但是,他们没有 allocate A,所以通常他们也不应该删除它。

这个问题有几种解决方案:

  1. 使用对象而不是指针- 这大大简化了内存管理,因为编译器为您做了正确的事情。
  2. 在's 和's 构造函数中创建一个新A对象BC- 你现在可以delete a,因为你拥有它。执行此操作时,您还必须实现一个复制构造函数和一个赋值运算符。
  3. 进行所有权的隐式转移- 这是调用者最棘手的问题:一旦调用者传递ABor的构造函数C,调用者必须立即停止使用对象指针。BC承担他们的所有权,A在析构函数中将其删除。
  4. 传递拥有的对象- 如果您控制 and 的生命周期BC您可以确保A在这些对象的生命周期内存在。在这种情况下BC可能会引用A,但不会触摸析构函数中的指针。

如您所见,该语言在控制所有权方面为您提供了很大的灵活性。一般来说,你应该从最简单的模型开始,当更简单的模型不再适合你的需要时,你应该开始使用更复杂的模型。

于 2013-10-26T23:39:16.227 回答
2

大多数情况下,您无需在堆上分配内存(也就是说,您无需调用new)即可摆脱困境。如果您来自托管语言(Java、C# 等),则很难改掉这个习惯。根据您的设计目标,您的代码可以写成

A a;
B b(a);
C c(a);
D d(b, c);

如果您的设计确实需要指针和动态内存使用,您应该使用智能指针包装器(std::unique_ptrstd::shared_ptr)。在这种特殊情况下,我建议std::shared_ptr您在多个地方使用(例如共享)指针:

std::shared_ptr<A> pA = std::make_shared<A>();
std::shared_ptr<B> pB = std::make_shared<B>(a);
std::shared_ptr<C> pC = std::make_shared<C>(a);
std::shared_ptr<D> pD = std::make_shared<D>(b, c);
于 2013-10-26T23:40:26.017 回答
2

使用指针时,您需要确保指针拥有适当的所有权:最简单的方法是创建单独的所有者,例如

std::unique_ptr<A> a(new A());
std::unique_ptr<B> b(new B(a.get()));
std::unique_ptr<C> c(new C(a.get()));
std::unique_ptr<D> d(new D(b.get(), c.get());

没有一个析构函数会做任何事情,因为std::unique_ptr<T>s 将拥有对象并释放它们。也就是说,指针用作链接而不是资源句柄。

当然,您甚至可能根本不在堆上分配任何内存就可以逃脱:

A a;
B b(&a);
C c(&c);
D d(&b, &c);

如果您想确保只要有任何用户,分配对象就一直存在,那么使用std::shared_ptr<T>是适当的方法:指针用作共享资源句柄,使用引用计数是正确的方法。

于 2013-10-26T23:32:03.743 回答
2

您面临的问题是所有权问题。目前,B 类和 C 类都假设它们拥有在构造时传递给它们的 A 对象(我们知道这一点,因为在它们的析构函数中破坏了对象)。如果这些对象应该/继续拥有一个 A 对象,那么您必须创建第二个 A。但是,如果它们可以共享该对象,则 B 和 C 都不应该负责删除该对象(因为它是共享的)。解决此问题的一种方法是将 B 和 C 更改为仅知道A 对象。这意味着 A 对象将需要被其他人删除。一种方法是使用 shared_ptr。

于 2013-10-26T23:36:16.857 回答