智能指针诞生之前

时间:2012-09-08 18:17:12

标签: c++ dynamic-allocation

在智能指针(能够获取动态区域中的资源所有权并在使用后释放它们)出现之前,我想知道当作为参数传递给占用资源指针的函数时,动态创建的对象的簿记是如何执行的。

通过簿记,我的意思是如果有一个“新”,那么在某个时候之后应该有一个“删除”。否则,程序将遭受内存泄漏。

这是一个例子,B是一个类,而void a_function(B *)是第三方库函数:

void main() {

  B* b = new B(); // line1

  a_function(b);  // line2

  ???             // line3
}

我在第3行做什么?我是否认为第三方功能已经解除了内存的分配?如果没有,我认为它有,那么我的程序会遇到内存泄漏。但是,如果它取消分配b占用的内存而且我也在main()中执行它以便安全,那么b实际上最终会被释放两次!由于双重免费错误,我的程序会崩溃!

10 个答案:

答案 0 :(得分:5)

启用“智能指针”的两个核心语言功能,更常见的是范围限制资源管理的惯用语(SBRM,有时也称为RAII,用于“资源获取是初始化“),是:

  • 析构函数(自动goto s)

  • 无约束变量(每个对象可以作为变量出现)

这些都是C ++的基本核心功能,并且一直是语言的一部分。因此,智能指针在C ++中一直是无法实现的。

[顺便提一下,这两个功能意味着C中的goto 必要以系统,一般的方式处理资源分配和多个退出,而它们基本上是禁止的< / em>在C ++中。 C ++将goto吸收到核心语言中。]

与任何语言一样,人们需要很长时间才能学习,理解和采用“正确”的习语。特别是考虑到C ++与C的历史联系,许多正在和正在从事C ++项目的程序员都来自C背景,并且可能会发现使用熟悉的模式更加舒适,这些模式仍然受到C ++的支持,即使这些模式仍然是不建议(“只需将malloc替换为new所有人,我们就可以发货了”。

答案 1 :(得分:2)

好的,不要讨论为什么这不相关,你应该使用智能指针......

所有其他条件相同(没有自定义分配器或任何类似的东西)规则是分配内存的人应释放内存。第三方功能,例如在你的例子中,应该绝对不会释放它没有创建的内存,主要是因为1)这是一般的糟糕做法(可怕的代码味道),更重要的是2 )它不知道如何分配内存以开头。想象一下:

int main()
{
    void * memory = malloc(sizeof(int));
    some_awesome_function(memory);
}

// meanwhile, in a third-party library...

void some_awesome_function(void * data)
{
    delete data;
}

如果malloc/freenew/delete使用不同的分配器运行会怎样?您正在查看某种可能的错误,因为用于delete的分配器不知道如何处理由malloc的分配器分配的内存。您<{>>永远 free内存为new',而您永远不会delete内存为malloc'd。如初。

至于第一点,事实上你必须要知道如果第三方库解除分配内存并且你试图(或者没有尝试)手动释放它会发生什么,这正是为什么事情不应该是这样做:因为你根本无法知道。因此,公认的做法是,无论代码的哪个部分负责分配,都要负责解除分配。如果每个人都坚持这个规则,每个人都可以记录他们的记忆,没有人会猜测。

答案 2 :(得分:0)

你摧毁你创造的东西,图书馆摧毁它创造的东西。

如果您与库共享数据(例如,文件数据的char *),库的文档将指定它是否保留对您的数据的引用(在这种情况下,在库完成之前不要删除您的副本使用它)或制作数据的副本(在这种情况下,完成后删除数据是图书馆的工作)。

答案 3 :(得分:0)

我看到很多人指出智能指针已经从C ++开始了。但事实是,即使在今天,并非所有代码都使用它们。一种常见的方法是手动进行引用计数:

void main() {
  B* b = createB(); //refcount = 1 
  a_function(b);
  releaseB(b); //--refcount
}

void a_function(B* b) {
  acquireB(b); //refcount++ when we store the reference somewhere
  ...
}

答案 4 :(得分:0)

智能指针是一种简化策略实施的方法。使用了相同的政策(将删除责任归咎于一个所有者或一组所有者)。您只需记录政策,不要忘记采取相应行动。智能指针既是记录所选策略并同时实现它的一种方式。在您的情况下,您查看了a_function文档并查看了它的要求。如果没有记录,或者采取或多或少受过教育的猜测。

答案 5 :(得分:0)

  

我在第3行做什么?

您可以参考a_function的文档。通常的规则是,除非他们这样做,否则函数 关于所有权或生命周期。通过引用C API来明确建立对此类文档的需求,其中智能指针不可用。

因此,如果它没有说它删除了它的参数,那么它就不会。如果它没有说它将其参数的副本保留到它返回的时间之外,直到其他指定的时间,那么它就没有。

如果它表示你采取了相应的行动,如果它没有说明,那么你delete b(或者最好是你写B b; a_function(&b); - 观察通过不破坏对象,该功能不需要要关心你如何创建对象,你可以自由决定。)

希望它能说出明确的内容,但是如果你运气不好,它会通过某种惯例说明API中的某些功能取得了参数所引用的对象的所有权。例如,如果它被称为set_global_B_instance,那么你可能会怀疑它会将指针保持在周围,并在设置之后立即将其删除是不明智的。

如果它没有任何说法,但是你的代码最终出错了,你最终发现a_function在其论证中调用delete,那么你会找到记录a_function和你把它们打在鼻子上在他们的文件中提交一个错误。

这个人经常是自己,在这种情况下尝试学习课程 - 文档对象所有权。

除了帮助避免编码错误之外,智能指针还为接受或返回有所有权问题的指针的函数提供了一定程度的自我文档。在没有自我文档的情况下,您有实际的文档。例如,如果函数返回auto_ptr而不是原始指针,则告诉您需要在指针上调用delete。您可以让auto_ptr为您执行此操作,也可以将其分配给其他智能指针,或者您可以release()指针并自行管理。您调用的函数不关心,也不需要记录任何内容。如果一个函数返回一个原始指针,那么它必须告诉你关于指针引用的对象的生命周期,因为你无法猜测。

答案 6 :(得分:0)

答案在第三方函数a_function()的文档中。可能的情况可能是:

  • 该函数只使用对象中的数据,并且在函数调用结束后不会保留对它的引用(例如:printf)。在函数调用结束后,您可以安全地删除该对象。
  • 该函数(在某些内部库对象中)将保留对该对象的引用,直到稍后调用(比如说b_function())。您有责任删除该对象,但必须保持该活动状态,直到您致电b_function(例如:strtok)。
  • 该函数获取对象的所有权,并且不保证对象在被调用后存在(例如:free())。在这种情况下,文档通常会指定如何创建对象(mallocnewmy_library_malloc)。

这些只是可能的许多不同行为的一些例子,但只要功能记录得足够好,你应该能够做正确的事。

答案 7 :(得分:0)

只需查看C API以获取提示。 C API提供显式的创建和销毁功能是很常见的。这些通常遵循库中的一些正式命名约定。

使用您的示例,如果a_function删除/释放参数(如果它没有明确标记为destroy函数),那将是一个糟糕的设计(在这种情况下,您不应在调用函数后使用该参数)。在大多数情况下,假设破坏你不拥有的对象是安全的是一个糟糕的设计。当然,使用智能指针,所有权机制,生命周期和清理通常由智能指针尽可能。

所以,是的,人们使用了newdelete,虽然我没有在template之前编写C ++ - 但看到明确的{{1}更常见和程序中的new。智能指针不是传递对象和传达所有权的非常好的手段,而delete s除了例外之外,它们是在1990年左右(C ++可用7年后)引入的。当然,编译器需要一些时间来支持所有这些功能,并且人们可以实现容器并改进这些实现。请注意,在模板之前可以实现,但实现/克隆任意类型的容器并不总是可行的,因为该语言在模板之前不支持泛型。当然,具体类型的具体类可以很容易地实现智能指针的机制,其中类型在那些日子里是不变的......但是当泛型不可用时,这确实会导致代码重复的形式。

但是即使在今天,除非明确标注,否则智能指针参数的内容对象被替换或销毁是一种不寻常的设计。这种可能性也会降低,因为将智能指针作为参数传递,而不是传递给它的对象也是不常见的。因此,与此相关的内存相关错误数量已经减少,但仍应遵守一些谨慎和良好的所有权惯例。

答案 8 :(得分:0)

简单回答:阅读文档。这在C接口中是常见的,因为如果函数声明对象的所有权,资源管理是接口的重要部分,它将被记录。

答案 9 :(得分:0)

如果没有智能指针,程序员通常会采用分配的实体负责解除分配的规则。

在您的示例中,通常会将第三方函数的错误行为(尽管是有效代码)视为删除传递给它的指针,并且您应该在第3行中将其删除。

这是程序员之间的社会契约,编译器通常不会强制执行此操作。