operator new在C++中的各种写法总结
本文导语: 乍一看,在C++中动态分配内存很简单:new是分配,delete是释放,就这么简单。然而,这篇文章讲得要复杂一点,并且要考虑到自定义层次。这也许对简单的程序并不重要,但对你在代码中控制内存却是十分必要的,是否能写一...
乍一看,在C++中动态分配内存很简单:new是分配,delete是释放,就这么简单。然而,这篇文章讲得要复杂一点,并且要考虑到自定义层次。这也许对简单的程序并不重要,但对你在代码中控制内存却是十分必要的,是否能写一个自定义的分配器,某种高级内存管理表或一个特定的垃圾回收机制。
这篇文章并不是一个综合的手册,而是一个C++中各种内存分配方法的概述。它面向已经很熟悉C++语言的读者。
原生operator new
我们先从原生operator new开始。考虑如下代码,它用来分配5个int型的空间并返回指向他们的指针[1]:
int* v = static_cast(::operator new(5 * sizeof(*v)));
当像如上的调用,operator new扮演原生的内存分配角色,类似malloc。上面等价于:
int* v = static_cast(malloc(5 * sizeof(*v)));
释放用operator new分配的内存用operator delete:
::operator delete(v);
你愿意永远用原生new和delete函数吗?是,只在极少数不用,我在下面的文章中会论证的。为什么用它们而不用原来的可信的malloc和free呢?一个很充分的原因就是你想保持代码在C++领域的完整性。混合使用new和free(或malloc和delete)是很不可取的(big NO NO)。用new和delete的另一个原因是你可以重载(overload)或重写(override)这些函数,只要你需要。下面是个例子:
void* operator new(size_t sz) throw (std::bad_alloc)
{
cerr ~Foo();
operator delete(p);
}
这时正适合我重复这篇文章第一段提到的,如果一个类有它自己的operator new或 operator delete,这些函数将被调用,而不是调用全局的函数来分配和收回内存。
Placement new
现在,回来我们上面看到样例代码中的"placement new"问题。它恰好真的能用在C++代码中的语法。首先,我想简单地解释它如何工作。然后,我们将看到它在什么时候有用。
直接调用 placement new会跳过对象分配的第一步。也就是说我们不会向操作系统请求内存。而是告诉它有一块内存用来构造对象[3]。下面的代码表明了这点:
int main(int argc, const char* argv[])
{
// A "normal" allocation. Asks the OS for memory, so we
// don't actually know where this ends up pointing.
//一个正常的分配。向操作系统请求内存,所以我们并不知道它指向哪里
int* iptr = new int;
cerr ~Foo();
对,显式调用析构函数在C++中是合法的,并且这也是唯一一种正确的做法[5]。
结论
这是一个复杂的主题,并且这篇文章只起到一个介绍的作用,对C++的多种内存分配方法给出了一种“尝鲜”。一旦你研究一些细节会发现还有许多有趣的编程技巧(例如,实现一个内存池分配)。这些问题最好是在有上下文的情况下提出,而不是作为一个普通的介绍性文章的一部分。如果你想知道得更多,请查阅下面的资源列表。
资源
· C++ FAQ Lite, especially items 11.14 and 16.9
· "The C++ Programming Language, 3rd edition" by Bjarne Stroustrup – 10.4.11
· "Effective C++, 3rd edition" by Scott Myers – item 52
· "Modern C++ Design" by Andrei Alexandrescu – chapter 4
· Several StackOverflow discussions. Start with this one and browse as long as your patience lasts.
我仍会在operator new前面显式地写::(双冒号),虽然这里并不是必须的。恕我直言,这是一个很好的做法,特别当在重载operator new的类中,可以避免二义性。
[2]
注意到这里是检查是否为NULL。这样做使delete p 很安全,即使p是NULL。
[3]
对传给placement new的指针确保有足够的内存分配给对象,并且确保它们正确地对齐,这都是你的应该做的。
[4]
内存池本身是一个很大且迷人的话题。我并不打算在这里扩展,所以我鼓励你自己上网找些信息,WIKI如往常一样是个好地方(good start)。
[5]
事实上,标准的vector容器用这种方法去析构它保存的数据。