指针类成员和删除职责

Pointer class member and deleting responsibilities

本文关键字:删除 成员 指针      更新时间:2023-10-16

如果我的类有一个可以由类客户端设置的指针,我应该如何处理删除?

示例:

class A {
};
class B {
public:
void setA(A* a) {
this->a = a;
}
private:
A* a;
};

B的析构函数应该是什么?是否应该删除a?正如我所看到的,用户可以通过两种方式设置这个指针:

... // Assume B object b being created elsewhere
A aObj;
A* aPtr = new A();
b.setA(&aObj); // It is not OK to use delete, and class member will
// point to invalid memory location once aObj goes out
// of scope
b.setA(aPtr);  // Using new will make the pointer available even after
// this block of code
...

那么删除b的正确方法是什么呢?我是否应该在set方法中始终执行new

类B的析构函数应该是什么?它应该删除吗?

类的作者您决定其语义。不要从指针、引用和delete的角度思考。从设计的角度思考:AB之间有什么关系?B需要A做什么?

两种常见的关系类型是委托和组合。委派意味着客户端代码使用setA成员让实例知道它可以用于进一步使用的其他B实例。组合意味着setA成员用于初始化实例的内部部分。

委派的一种可能实现是使用成员指针。我建议传递一个对setA的引用来分配给那个指针;它避开了检查CCD_ 14的问题,并使客户端代码清楚地知道不存在需要处理的所有权问题。这与多态类型兼容。

一种可能的组合实现是使用A成员,并通过引用传递给const或通过值分配给它。另一种是使用智能指针,特别是如果A是多晶型使用的。传递智能指针是最简单的方法,但您必须检查0和/或记录转换。

无论你决定使用什么(无论如何都不必在这个列表中),都要将代码作为实现你的目的或设计的工具。不要让代码支配你的想法。

在我的所有示例实现中,您不必在析构函数中做任何特殊的操作。

您真的不应该有这样的类。相反,使用像shared_ptrunique_ptr这样的资源管理容器来保存指向动态分配对象的指针。

正如你很容易看到的,如果你在各处随机分配动态对象,你就无法追踪谁应该对此负责。你们班的临摹和作业怎么样?构造函数中的异常怎么办?不要这么做。

我认为通常会有3种场景,请参阅下面的代码:

//class B doesn't own a
class B {
public:
void setA(A& a) {
m_a = a;
}
private:
A& m_a;              //Only a reference , so need to worry about delete
};

//class B owns A
class B {
public:
void setA(std::auto_ptr<A>& a) {
m_a.reset(a.release());
}
private:
boost::scoped_ptr<A> m_a;   //m_a got deleted when instance of B lifetime end
};

//class B shared A with someone else 
class B {
public:
void setA(boost::shared_ptr<A>& a) {
m_a = a;
}
private:
boost::shared_ptr<A> m_a;   //m_a got deleted when no one need this pointer anymore(reference counting reduced to 0)
};

您需要做出设计决策。谁应该拥有这个物体?

  • B对象拥有A对象,setA将所有权传递给B。B的析构函数应该删除A
  • 某些外部代码拥有A对象。B不会删除它,但将依靠外部代码在适当的时候销毁它
  • 智能指针跟踪对A对象的引用,并在所有引用被销毁时自动将其删除

带有智能指针的第三个选项是最简单、最可靠的,但这三个选项都可以使用。诀窍是选择一个并慎重考虑

在设计分析过程中,您必须回答以下问题:

  1. 对象A是否取决于对象B的生存期,如果是,则使用"composition",在这种情况下,对象B将创建A,并负责在对象B自身被销毁之前删除它
  2. 如果对象A与对象B的生存期无关,则使用"聚合"。通过B的构造函数或set方法将对象A提供给B。对象B不必担心破坏对象A,但您必须确保在B的生命周期内,A处于有效状态
  3. 如果A依赖于对象B的生存期,但您需要在B之前创建A,则执行"依赖注入"。这就像聚合,因为你可以在构造函数或set方法中将A传递给B,但在这种情况下,A只由B使用,没有其他对象使用A。B在销毁A之前删除了A

从外观上看,您正试图在B类中创建对a对象的引用。

要正确清理B,您必须首先检查A是否为空。类似。。。

~B()
{
if (A)
{
delete A;
A = 0;
}
}

请记住,这也会删除类之外的A对象,因为它们引用的是相同的内存。因此,在这样的情况下,如果删除a对象,您可以很容易地引用指向无效内存的指针。

顺便说一句,我也不会把它和局部变量一起使用,因为当它超出范围时,你会丢失它的地址。然而,另一方面,本地指针。。那么,一旦离开了创建A的范围,就不必担心引用无效内存了。