我最近遇到了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
?根据需要创建语法 - 什么是理想,只是为了帮助我更好地理解这些问题。
答案 0 :(得分:5)
嗯,理想可能不需要删除任何类型。有一个垃圾收集环境,让程序员避免整个问题。
咆哮中的抱怨似乎归结为
他对于你必须同时实现new
和new[]
这一令人讨厌的事实是正确的,但是你被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个数组元素的构造函数。
此代码还存在其他问题,但不是作者列出的问题。