C++内存管理技术/实践

12

我开始用C++编写一个项目,这种语言中的内存管理对我来说是新的。

以前我使用new()创建对象,然后传递指针,虽然它能够工作,但调试时很麻烦,而且人们在看到代码时会觉得奇怪。我很自豪的是它没有泄漏或段错误(修复后),但确实需要付出很多努力。

list <struct Connection *> users;

struct Connection * accept_connection (const char *name) {
  struct Connection * new_node = new Connection ();
  new_node->_data = ... // whatever, set it up here
  return new_node;
}

struct Connection * new_user = accept_connection (const char *name);
users.append (new_user);
所以我有一个新的策略用于这个项目的下一版。到目前为止,我的做法是使用 new() 创建对象并为它们分配唯一的整数ID号码。然后,我使用该ID作为键将对象存储在哈希表中。现在,项目通过整数ID号码存储和传递,当您需要取消引用时,只需访问哈希表,它会返回thing *NULL。因此我不再经历指针错误,但代码的速度有所降低。
typedef unsigned long ID_Number;

// create a user and return the ID
ID_Number create_user () {
  ID_Number new_id = assign_unique_id ();
  struct User * node = new User ();
  node->_id = new_id;
  node->_data = ... // whatever, set it up here
  add_to_users_dict (new_id, node);
  return new_id;
}

list <ID_Number> users;

for_each (users.begin(), users.end(), process_user);

void process_user (ID_Number i) {
  struct User * u_ptr = lookup_user_dict (i);
  if (u_ptr == NULL) {
    // not found in dict
    // somehow this guy was deleted
  } else {
    // we can do stuff with this guy
  }
}

虽然我对编程的基本原则有所了解,但作为一个自学的业余爱好者,我不熟悉行业惯例和工具。

我基本上要求的是内存管理指南:

1)我做得对还是错?

2)是否有任何可以帮助我的软件包或库?

3)行业标准惯例是什么?

4)基本上我应该搜索什么或购买Kindle等阅读器?

现在我通常使用Python,它可以处理很多“后端”事务,但出于速度/性能方面的原因,我需要使用C或C ++(我猜我正在使用纯C加上stdc ++库,我不太确定这两种语言之间的重叠区域在哪里 - 我只知道g ++可以编译它并且运行良好),特别是针对某个项目:尽管我怀疑一些数学天才可能提供算法修复程序来大幅提高速度,但那是一个单独的问题。


2
shared_ptr 开始看起。 - Mark Ransom
我会使用的第一个工具是valgrind:http://valgrind.org/docs/manual/QuickStart.html - Anycorn
11
了解RAII。 这有点疯狂。 - Edward Strange
5
听起来你会从一本好的 C++ 书籍中受益匪浅。 - R. Martinho Fernandes
可能是C ++中的内存管理的重复问题。 - Mike Seymour
@R Martinho Fernandes - 你能推荐其中一个吗?我认为我需要RAII和工厂模式。我将把这个决定留给那些比我更懂C++的人,但是如果是一本“如何编译hello world”的书,我会感到有点无聊。尽管我不是C++方面的专家,可能还有我不知道需要了解的事情。 - user774340
4个回答

10
我能够给出的最佳答案是,您不应该以传统方式使用指针。C++11改变了程序员处理内存管理的方式。
与其解释已经被比我聪明得多的人详细解释过的事情,我将提供一些链接。
您应该首先查看Herb Sutter的文章 现代C++风格的要素,然后观看Bjarne Stroustrup的视频 C++11风格
如果您能够使用新的C++11标准,那么应该使用它,它使内存管理比以前更加清洁。

2
如果你不能使用C++11(打电话给猎头!),那么有很多不需要手动管理内存的东西可以与C++03一起使用。 - R. Martinho Fernandes

2

我做得对或错了什么?

您实际上创建了一个使用句柄来引用对象而不是直接指针的系统。这在某些情况下可能是适当的。操作系统通常在操作系统“拥有”对象并管理其生命周期但允许客户端引用它时使用句柄。

是否有任何可用的程序包或库可以帮助我?

现代C++标准库具有shared_ptr和unique_ptr,这是管理动态对象的智能指针。它们是使用RAII的好方法。

业界的标准做法是什么?

C ++中的事实标准是RAII - 资源分配是初始化。RAII将分配与构造函数绑定,将释放与析构函数绑定。由于C ++对c'tors和d'tors执行的方式和时间有坚实的保证,因此这为您提供了一种完美的方法来管理对象生命周期,无泄漏。智能指针shared_ptr和unique_ptr还使对象所有权明确。

基本上我应该搜索或购买什么?

搜索RAII。


2
你的第一个错误是使用new
动态内存很少需要,而且直接需要的情况更少:大多数动态分配的对象都存在于容器中(如vectormap)。
你的第二个错误是不使用构造函数。一旦你理解了类不变量以及构造函数如何启用它们,你就能利用RAII(资源获取即初始化)并停止使用C编程。

我开始研究RAII - 将ctor/dtor包装起来,这样当包装从堆栈中移除时,dtor会自动调用。那么使用RAII,如何将一个东西的单个实例放入两个不同的列表中呢?我需要使用引用吗?然后如何阻止列表B遍历列表A说要删除的内容?似乎我需要在每个结构体中添加一个"is_deleted_skip_me"布尔值(并定期删除它们),或者每当单个列表更改时,我需要保持清理我的列表?--编辑:如果需要这么多单独的列表,我开始认为应用程序设计得很糟糕。 - user774340
@gecko:许多相互依赖的列表看起来很糟糕,尽管我不知道你的限制。你不能复制信息,使每个列表都有自己的副本吗?否则,总有可能使用shared_ptr,但这会在未来带来一些困难。 - Matthieu M.
盒子(数据对象)位于容器内(容器本身是同一结构的不同实例 - 只有名称和容量不同),因此容器具有其内容列表。代理记住它最后接触的N个项目。因此,如果代理将盒子放入容器中,则有两个包含相同项目的列表。 - user774340
@gecko:我可以看到两种解决方法。第一种是几乎共享所有权(shared_ptr),代理使用weak_ptr进行引用。第二种是基于通知的机制。您可以将代理作为一个盒子的观察者(这是一种设计模式),并让盒子在即将被销毁时通知其观察者,以便它们在实际销毁之前注销它。这是异步和同步检测销毁的区别,权衡因素各不相同。 - Matthieu M.

1

1) 我做得对还是错了?

您没有使用资源获取即初始化(RAII)习惯用语或现代C++所有权语义。

2) 有哪些包或库可以帮助我?

如果您确实需要传递指针,可以使用std::unique_ptr和std::shared_ptr。但在使用之前,您应该学习如何使自己的对象具有RAII语义,以便作为资源所有者。

3) 行业标准做法是什么?

4) 基本上我应该搜索或购买什么?

RAII


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接