我正在尝试使用一些附加信息来new
,以便追踪内存泄漏。我知道,我可以全局覆盖new
运算符,但我惊讶地发现我无法检索有关所分配对象类型的任何信息(如果我错了,请纠正我)。显然,当您决定覆盖new
运算符时,获取类型信息会很有用。
例如,我使用可变参数模板实现了new
和delete
的简单通用版本。
std::string to_readable_name(const char * str)
{
int status;
char *demangled_name = abi::__cxa_demangle(str, NULL, NULL, &status);
if(status == 0) {
std::string name(demangled_name);
std::free(demangled_name);
return name;
}
return "Unknown";
}
template <typename T, typename... Args>
T * generic_new(Args&&... args) throw (std::bad_alloc)
{
const std::size_t size = sizeof(T);
std::cout << "Allocating " << size << " bytes for " << to_readable_name(typeid(T).name()) << std::endl;
return new T(std::forward<Args>(args)...);
};
template<typename T>
void generic_delete(T* ptr)
{
const std::size_t size = sizeof(T);
std::cout << "Deleting " << size << " bytes for " << to_readable_name(typeid(T).name()) << std::endl;
delete ptr;
}
int main()
{
auto i = generic_new<int>(0);
std::cout << *i << std::endl;
generic_delete(i);
return 0;
}
我的问题是为什么new
没有用模板实现?这将允许开发人员获得有关正在分配的对象类型的信息。
谢谢
答案 0 :(得分:5)
关于为什么设计C ++的大多数问题都归结为两种可能性。 C ++的设计和演变给出了一个理由,或者我们只能推测。我相信这属于后一类。
首先,使用(以及替换)operator new
(全局和每类)的功能早于向该语言添加模板。因此,最初可能无法使用模板。图书馆的某些部分被转换为以前没有的模板,但大多数情况下,这样做的好处非常明显。
其次,使用具有早期模板功能的编译器的模板可能会导致问题,尤其是对于较大的程序。问题是功能模板不是一个功能 - 它可以说是创建功能的秘诀。换句话说,如果它是作为模板实现的,那么每种类型的每个分配器都会导致实例化一个单独的函数。目前的编译器(和链接器)在合并后很好,但早期的编译器大多数都不是。在大型系统中,您可以轻松创建数十个甚至数百个单独的分配函数。
最后,模板通常仍然只是普通用户代码和库中某些之间的中间级别,提供至少与当前operator new
和{最近大致一致的内容“。 {1}}与操作系统(或硬件或其他)交谈,以确定从哪个池中抽取内存,并(通常)将片段分配给用户代码。在某个地方,您需要一段(通常相当大的)代码来创建和管理内存池。如果这是直接在模板中处理的话,它(实际上)必须在头文件中实现,这也会导致编译时间在66 MHz奔腾甚至是令人难以置信的快速300 MHz DEC上可能是相当不可接受的。 Alpha机器我们大多数人都只能梦想。
答案 1 :(得分:4)
You can not override the global operator new. 可以做的是提供替换 - 在链接期间而不是在编译期间发生。
我的问题是为什么没有新的模板实现?
由于new
基本上是一个未定义的引用,因此您可以轻松提供单个定义来替换它。 (见下面的细节#1)
现在如果new
是模板化函数会怎么样?对于每个模板实例,都会有一个不同的未定义符号,您需要为其提供替换。您也可以将您的定义作为模板,但谁会实例化它?您可能需要为此更改名称解析规则(基本上是模板的工作方式)。
或者您反而允许operator new
覆盖。然后编译器必须在编译期间选择正确的operator new
。但这样做的缺点是,只有具有您的&#34;新new
&#34;在其翻译单位可以查看和使用它。现在考虑一下这里发生了什么:
#include <memory>
// code to override new and delete
void bar(void) {
std::unique_ptr<int> x = new int{};
}
以上对new
的调用使用了您的代码,但是对delete
的调用深埋在标准库的内部呢?该调用也需要查看您的代码。这反过来意味着标准库代码必须在您的翻译单元中,或者换句话说:它需要在头文件中实现。
此外,您需要确保使用char *
分配的 - { - 1}}并未通过&#获得new
d 34;标准&#34; delete
。类特定运算符已存在类似问题:
delete
这实际上是调用特定于类的运算符class Baz; // forward declaration
void freeThatBaz(Baz * b) { delete b; }
还是全局调用实现定义(因为C ++ 17,它在IIRC之前是未定义的行为)。
所以我要说:基本上不值得努力在标准中制定如此庞大而复杂的规则,而这些规则甚至没有考虑过#34;良好做法&#34 ;
编译以下代码:
delete
导致
void foo(void) {
auto x = new int{};
auto y = new bool{};
delete x;
delete y;
}
[nix-shell:/tmp/wtf]$ nm -C new.o
U _GLOBAL_OFFSET_TABLE_
0000000000000000 T foo()
U operator delete(void*, unsigned long)
U operator new(unsigned long)
的单个未定义引用,与类型无关。如果 是模板,则模板类型参数将成为符号的一部分,因此您有2个未定义的符号。