浅层破坏者和深层破坏者?

时间:2019-07-24 09:40:23

标签: c memory-management dynamic-memory-allocation allocation

想象一个列表“ a”,并且有一个复制构造函数用于列表,该列表执行深度复制。如果“ b”是从“ a”深复制的列表,则可以使用简单的析构函数销毁两个列表。此析构函数应使用 deep 破坏。

typedef struct list { void * first; struct list * next } list;

struct list * list_copy_constructor(const struct list * input)
REQUIRE_RETURNED_VALUE_CAPTURE;

void list_destructor(struct list * input);

现在,假设您将列表的副本构造函数重命名为列表的深层副本构造函数,并为列表添加另一个浅表副本构造函数。

/**  Performs shallow copy. */
struct list * list_shallow_copy_constructor(const struct list * input)
REQUIRE_RETURNED_VALUE_CAPTURE;

/**  Performs deep copy. */
struct list * list_deep_copy_constructor(const struct list * input)
REQUIRE_RETURNED_VALUE_CAPTURE;

/**  Be warned performs deep destruction. */
void list_destructor(struct list * input);

执行深度销毁的析构函数可以与深度拷贝构造函数调用配对使用。

一旦对列表使用了浅表复制构造函数,就需要知道两个列表中的哪个拥有元素,然后可以破坏其中一个列表(拥有元素的列表)使用析构函数,但是对于不拥有元素的列表,在销毁拥有元素的列表之前,我需要使用浅析构函数销毁它。 >

/**  Performs shallow copy. */
struct list * list_shallow_copy_constructor(const struct list * input)
REQUIRE_RETURNED_VALUE_CAPTURE;

/**  Performs deep copy. */
struct list * list_deep_copy_constructor(const struct list * input)
REQUIRE_RETURNED_VALUE_CAPTURE;

/**  Performs shallow destruction. */
void list_shallow_destructor(struct list * input);

/**  Performs deep destruction. */
void list_deep_destructor(struct list * input);

但是,问题是,我不认为浅析构函数是书目中的术语,所以我认为我可能做错了什么。 我做错什么了吗?例如我应该已经在使用智能指针而不是深浅析构函数了吗?

4 个答案:

答案 0 :(得分:1)

深层或浅层的概念仅存在于程序员的脑海中,而在c ++中则是非常任意的。默认情况下,销毁对象时原始指针成员不会被深度销毁,您可以将其称为浅对象,但是您可以在对象析构函数中编写额外的代码来深度销毁。另一方面,任何具有析构函数的成员都会调用其析构函数,您可能会对其进行深入调用,并且没有办法避免这种情况。完全相同的机制适用于默认的复制和赋值,因此一眼就能看出一个对象是完全深浅复制或销毁是完全不可能的。

所以区别实际上不是对象析构函数的属性,而是对象的成员。

当然,现在的问题还是关于C的,但是仍然提到了智能指针。在C语言中,您必须决定要实施的哲学,因为没有破坏的概念。如果您遵循类似c ++的哲学,即对每种类型的成员都具有销毁功能,并对其进行深度调用。

已经说过,您可能会考虑许多可能产生更精简模型的策略:

我/ all /特定容器的成员是拥有的,或/ all /不拥有,那么在容器中使用一个简单的标志来确定是否销毁/ all /子代就足够了。

如果/ all /对象与另一个容器共享,或者这可能是特定内容集的最后一个/唯一的容器,则可以保留一个共享容器的循环列表。当析构函数意识到这是最后一个容器时,它可以销毁所有内容。另一方面,您可以使用一个容器实例的shared_ptr来简单地实现此模型,并在释放最后一个指针时破坏该容器。

如果可以以任意方式共享容器中的各个项目,则将其设置为每个内容项的shared_ptr容器。这是最可靠的模型,但是在内存使用方面可能会产生成本。最终,在某个地方需要一个引用计数(尽管圆形​​的裁判列表也很不错,但跨线程互斥更加困难)在c ++ shared_ptr中,这是使用存根实现的,但是在您自己的C对象中,这可能是一个计数器子对象中的成员。

答案 1 :(得分:1)

如果要正确破坏具有共享元素的列表,则不能简单地使用“浅析构函数”。使用浅析构函数将它们全部破坏会导致元素仍驻留在内存中并被泄漏。标记其中一个列表具有深的析构函数,而其他列表具有浅的析构函数,这看起来也不好。如果您首先使用深层析构函数销毁该列表,则其他人将有悬挂的指针,您可能会意外访问它们。因此,看起来浅析构函数并不是一个公认的术语,仅仅是因为它没有太大用处。您只有析构函数:这些函数可以破坏您概念上拥有的对象。

现在,对于共享列表中的元素并及时销毁所有内容的特定任务,共享指针似乎是一个合理的解决方案。从概念上讲,共享指针是指向由两个元素组成的结构的指针:实际对象(列表元素)和引用计数器。复制共享指针会增加计数器;销毁共享指针将减少计数器,并在计数器降至0时使用对象和计数器破坏结构。在这种情况下,每个列表都拥有其共享指针的副本,但是列表元素本身由共享指针而非列表拥有。由于共享指针销毁的语义,销毁列表拥有的所有共享指针副本没有麻烦(析构函数不会释放内存,除非不存在引用),因此无需区分“浅”和“深度”析构函数,因为共享指针将负责及时自动删除自身。

答案 2 :(得分:1)

您已经怀疑,您的设计很奇怪。想一想:如果您要“浅表复制”列表,为什么不直接获取它的指针呢? “浅拷贝”在教室外没有用,在这里仅用于解释什么是“深拷贝”。

您要么希望多个用户拥有独立的列表,并且可以彼此独立地使用和销毁列表,要么您希望一个用户“拥有”该列表,而其他用户仅指向该列表。您的“浅表复制”构想比简单的指针没有优势,但是处理起来要复杂得多。

实际上有用的是拥有多个“列表”但共享数据,其中每个用户都有自己的列表“共享副本”,可以单独使用和销毁该列表,但指向相同的数据,并且只会当最后一个用户销毁它时将其释放。这是一种非常常见的模式,通常由称为reference counting的算法处理,并由许多库和语言(例如Python,glib,甚至在C ++中作为智能指针std::shared_ptr)实现。

如果您使用的是C,则可能希望为您的struct list添加对引用计数的支持,这并不是很难:只需添加一个字段unsigned reference_count;并将其设置为1即可。已分配。销毁时减少,但只有在reference_count == 0时才真正释放,在这种情况下,不再有用户,您必须进行“深度释放”。您仍然只有一个析构函数,但有两个副本构造函数:

/**  Performs shared copy.
 *
 * Actually, just increments reference_count and returns the same pointer.
 */
struct list * list_shared_copy_constructor(struct list * input)
REQUIRE_RETURNED_VALUE_CAPTURE;

/**  Performs deep copy.
 *
 * reference_count for the new copy is set to 1.
 */
struct list * list_deep_copy_constructor(const struct list * input)
REQUIRE_RETURNED_VALUE_CAPTURE;

/**  Performs destruction. */
void list_shallow_destructor(struct list * input);

如果您确实在使用C ++,如您在问题中所提示的,则只需使用std::shared_ptr

答案 3 :(得分:0)

  

但是,问题是,我不认为浅析构函数是书目中的术语,所以我认为我做错了什么。我在做错什么吗?

析构函数的目的(或在C语言中只是 myobject_destroy(myobject )*)是为了清理实例所拥有的资源(内存,操作系统句柄,...)。您是否需要“浅析构函数”或“深析构函数”,取决于您决定实现对象的方式,只要对象能够完成清理工作即可。

如果您使用的是现代C ++堆栈分配,并且智能指针是您的朋友,因为它们自己管理内存。