1

我最近遇到了这种咆哮

文章中提到的几点我不是很明白:

  • 作者提到了deletevs的小烦恼delete[],但似乎认为它实际上是必要的(对于编译器),但从未提供过解决方案。我错过了什么?
  • 在“专用分配器”部分的功能f()中,似乎可以通过将分配替换为以下内容来解决问题:(省略对齐)

    // if you're going to the trouble to implement an entire Arena for memory,
    // making an arena_ptr won't be much work. basically the same as an auto_ptr,
    // except that it knows which arena to deallocate from when destructed.
    arena_ptr<char> string(a); string.allocate(80);
    // or: arena_ptr<char> string; string.allocate(a, 80);
    arena_ptr<int> intp(a); intp.allocate();
    // or: arena_ptr<int> intp; intp.allocate(a);
    arena_ptr<foo> fp(a); fp.allocate();
    // or: arena_ptr<foo>; fp.allocate(a);
    // use templates in 'arena.allocate(...)' to determine that foo has
    // a constructor which needs to be called. do something similar
    // for destructors in '~arena_ptr()'.
    
  • 在“重载 ::operator new[] 的危险”中,作者试图做一个new(p) obj[10]. 为什么不这样做(远不那么模棱两可):

    obj *p = (obj *)special_malloc(sizeof(obj[10]));
    for(int i = 0; i < 10; ++i, ++p)
        new(p) obj;
    
  • '调试 C++ 中的内存分配'。不能在这里争论。

整篇文章似乎都围绕在自定义内存管理方案中具有重要 构造函数析构函数的类展开。虽然这可能很有用,而且我无法反驳,但它的共性非常有限。

基本上,我们有放置新的和按类分配器——这些方法不能解决哪些问题?

另外,如果我只是笨拙和疯狂,在理想的 C++ 中,什么会代替operator new?必要时发明语法——什么是理想的,只是为了帮助我更好地理解这些问题。

4

2 回答 2

5

好吧,理想的情况可能是不需要任何形式的删除。有一个垃圾收集的环境,让程序员避开整个问题。

咆哮中的抱怨似乎归结为

  1. “我喜欢 malloc 的方式”
  2. “我不喜欢被迫显式创建已知类型的对象”

new他对您必须同时实现and的恼人事实是正确的new[],但是由于 Stroustrups 维护 C 语义核心的愿望,您被迫这样做。由于您无法从数组中分辨出指针,因此您必须自己告诉编译器。你可以解决这个问题,但这样做意味着从根本上改变语言 C 部分的语义;你不能再使用身份

*(a+i) == a[i]

这将破坏所有 C 代码的一个非常大的子集。

所以,你可以有一种语言

  • 实现了一个更复杂的数组概念,并消除了指针算术的奇迹,用涂料向量或类似的东西实现数组。

  • 被垃圾收集,所以你不需要自己的delete纪律。

也就是说,你可以下载Java。然后,您可以通过更改语言来扩展它

  • 不是强类型的,因此void *消除了向上转换的类型检查,

...但这意味着您可以编写将 Foo 转换为 Bar 的代码,而编译器不会看到它。如果需要,这也将启用鸭式打字。

问题是,一旦你完成了这些事情,你就拥有了具有 C-ish 语法的 Python 或 Ruby。

自从 Stroustrup 发出 cfront 1.0 磁带以来,我一直在编写 C++;C++ 所涉及的许多历史都源于对拥有一种适合 C 世界的 OO 语言的渴望。大约在同一时间出现了许多其他更令人满意的语言,例如 Eiffel。C++ 似乎赢了。我怀疑它赢了是因为它可以融入 C 世界。

于 2009-04-14T00:05:26.933 回答
3

恕我直言,咆哮非常具有误导性,在我看来,作者确实了解更详细的细节,只是他似乎想误导。恕我直言,显示论点缺陷的关键点如下:

void* operator new(std::size_t size, void* ptr) throw();

该标准定义上述函数具有以下属性:

返回:ptr。

注意: 有意不执行其他操作。

重申一下 - 此函数有意不执行其他操作。这非常重要,因为它是放置 new 的关键:它用于调用对象的构造函数,仅此而已。请明确注意,甚至没有提到size参数。

对于那些没有时间的人,总结一下我的观点:'malloc' 在 C 中所做的一切都可以在 C++ 中使用 "::operator new" 完成。唯一的区别是,如果您有非聚合类型,即。需要调用其析构函数和构造函数的类型,那么您需要调用这些构造函数和析构函数。此类类型在 C 中不明确存在,因此使用“malloc 做得更好”的论点是无效的。如果你在“C”中有一个结构,它有一个特殊的“initializeMe”函数,必须用相应的“destroyMe”调用,那么作者提出的所有观点同样适用于该结构,就像它们适用于非聚合 C++ 结构一样。

明确提出他的一些观点:

要实现多重继承,编译器必须在某些强制转换期间实际更改指针的值。当转换为 void * 时,它无法知道您最终想要哪个值......因此,没有一个普通函数可以在 C++ 中扮演 malloc 的角色——没有合适的返回类型。

这是不正确的,再次::operator new执行malloc的角色:

class A1 { };
class A2 { };
class B : public A1, public A2 { };

void foo () {
    void * v = ::operator new (sizeof (B));
    B * b = new (v) B();  // Placement new calls the constructor for B.
    delete v;

    v = ::operator new (sizeof(int));
    int * i = reinterpret_cast <int*> (v);
    delete v'
}

正如我上面提到的,我们需要placement new 来调用B 的构造函数。在'i' 的情况下,我们可以毫无问题地从void*转换为int* ,尽管再次使用placement new 会改进类型检查。

他提出的另一点是关于对齐要求:

new char[...] 返回的内存不一定满足结构 intlist 的对齐要求。

3.7.3.1/2 下的标准说:

返回的指针应适当对齐,以便它可以转换为任何完整对象类型的指针,然后用于访问已分配存储中的对象或数组(直到通过调用相应的释放函数显式释放存储) .

这对我来说似乎很清楚。

在专门的分配器下,作者描述了您可能遇到的潜在问题,例如。您需要将分配器用作任何自行分配内存的类型的参数,并且构造的对象将需要显式调用其析构函数。同样,这与将分配器对象传递给 C 结构的“initializeMe”调用有何不同?

关于调用析构函数,在 C++ 中,您可以轻松创建一种特殊类型的智能指针,我们称之为“placement_pointer”,我们可以定义它以在析构函数超出范围时显式调用它。结果,我们可以:

template <typename T>
class placement_pointer {
  // ...
  ~placement_pointer() {
    if (*count == 0) {
      m_b->~T();
    }
  }
  // ...
  T * m_b;
};

void
f ()
{
  arena a;

  // ...
  foo *fp = new (a) foo;           // must be destroyed
  // ...
  fp->~foo ();

  placement_pointer<foo> pfp = new (a) foo; // automatically !!destructed!!
  // ...
}

我要评论的最后一点是:

g++ 带有一个“放置”操作符 new[],定义如下:

inline void *
operator new[](size_t, void *place)
{
  return place;
}

如上所述,不仅以这种方式实现 - 而且标准要求如此。

设 obj 是一个带有析构函数的类。假设您在某处有 sizeof (obj[10]) 字节的内存,并希望在该位置构造 10 个 obj 类型的对象。(C++ 将 sizeof (obj[10]) 定义为 10 * sizeof (obj)。)你能用这个放置操作符 new[] 做到这一点吗?例如,以下代码似乎会这样做:

obj *
f ()
{
  void *p = special_malloc (sizeof (obj[10]));
  return new (p) obj[10];       // Serious trouble...
}

不幸的是,这段代码不正确。通常,不能保证传递给 operator new[] 的 size_t 参数确实对应于正在分配的数组的大小。

但正如他通过提供定义强调的那样,分配函数中没有使用 size 参数。分配函数什么都不做——所以上述放置表达式的唯一影响是调用 10 个数组元素的构造函数,正如您所期望的那样。

此代码还有其他问题,但不是作者列出的问题。

于 2009-04-15T11:02:52.197 回答