C ++'new'运算符的问题?

时间:2009-04-13 23:59:40

标签: c++ language-features new-operator allocation

我最近遇到了this rant

我不太明白文章中提到的一些要点:

  • 作者提到了delete vs 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?根据需要创建语法 - 什么是理想,只是为了帮助我更好地理解这些问题。

2 个答案:

答案 0 :(得分:5)

嗯,理想可能不需要删除任何类型。有一个垃圾收集环境,让程序员避免整个问题。

咆哮中的抱怨似乎归结为

  1. “我喜欢malloc的方式”
  2. “我不喜欢被迫明确创建已知类型的对象”
  3. 他对于你必须同时实现newnew[]这一令人讨厌的事实是正确的,但是你被Stroustrups希望维持C语义的核心所迫。由于无法从数组中指出指针,因此必须自己告诉编译器。你可以解决这个问题,但这样做意味着从根本上改变语言C部分的语义;你不能再使用身份

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

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

    所以,你可以使用一种语言

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

    • 是垃圾回收,因此您不需要自己的delete纪律。

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

    • 不是强类型的,因此类型检查void * upcast已消除,

    ...但这意味着您可以编写将Foo转换为Bar而无需编译器查看的代码。如果你需要,这也可以实现鸭式。

    问题是,一旦你做完了这些事情,你就会得到带有C-ish语法的Python或Ruby。

    我一直在编写C ++,因为Stroustrup发出了cfront 1.0的磁带; C ++中涉及的很多历史,因为它现在源于希望拥有适合C世界的OO语言。还有很多其他更令人满意的语言,就像埃菲尔一样出现在同一时间。 C ++似乎赢了。我怀疑它赢了因为它可以适应C世界。

答案 1 :(得分:3)

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

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

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

  

返回:ptr。

     

备注:故意不执行任何其他操作。

重申一下 - 此功能故意不执行任何其他操作。这非常重要,因为它是放置new的关键:它用于调用对象的构造函数,而这就是它所做的一切。请注意,甚至没有提到 size 参数。

对于那些没有时间的人,总结一下我的观点:'malloc'在C中所做的一切都可以使用“:: operator new”在C ++中完成。唯一的区别是,如果你有非聚合类型,即。需要调用析构函数和构造函数的类型,然后需要调用那些构造函数和析构函数。这些类型在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'
}

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

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

  

new char [...]返回的内存不一定符合struct intlist的对齐要求。

3.7.3.1/2下的标准说:

  

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

这对我来说非常清楚。

在专门的分配器下,作者描述了您可能遇到的潜在问题,例如:你需要使用allocator作为任何自己分配内存的类型的参数,并且构造的对象需要显式调用它们的析构函数。再次,将分配器对象传递给C结构的“initalizeMe”调用有何不同?

关于调用析构函数,在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 ++附带一个“placement”运算符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参数。分配函数 nothing - 所以上面的放置表达式的唯一影响就是调用10个数组元素的构造函数。

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