我的C ++教授向我们展示了重载运算符new的例子(我认为这是错误的):
class test {
// code
int *a;
int n;
public:
void* operator new(size_t);
};
void* test::operator new(size_t size) {
test *p;
p=(test*)malloc(size);
cout << "Input the size of array = ?";
cin >> p->n;
p->a = new int[p->n];
return p;
}
这是对的吗?
答案 0 :(得分:2)
这绝对是“不对的”,因为它给了我毛骨悚然。
由于test
没有用户声明的构造函数,我认为只要test
的实例没有进行值初始化(这会清除指针),它就可以工作。并且只要您编写相应的operator delete
。
但这显然是一个愚蠢的例子 - 在重载operator new
内的用户交互?如果在堆栈上创建test
的实例怎么办?还是复制?或者在C ++ 03中使用test *tp = new test();
创建?还是安置新的?几乎没有用户友好。
构造函数必须用于建立类不变量(例如“我有一个要使用的数组”),因为这是覆盖所有这些情况的唯一方法。因此,分配这样的数组是应该在构造函数中完成的事情,而不是在operator new
中。或者更好的是,改为使用vector
。
就标准而言 - 我认为由于该类是非POD,因此允许实现在调用operator new
之间遍布数据并将其返回给用户,因此这不是即使小心使用也能保证工作。不过,我不完全确定。可以想象,你的教授已经开始运作它(也许很多年前,当他第一次写这门课程时),如果是这样,它就可以在他的机器上运行。在这个类的特定情况下,实现不希望对内存做任何事情。
我认为这是“错误的”,因为他 访问之前的对象 构造
我认为你在这一点上也是正确的 - 将指针从malloc
转换为test*
并且访问成员是UB,因为类test
是非POD(因为它有私有的非静态数据成员)并且内存不包含类的构造实例。但是,我没有理由立即想到为什么一个实现会想要做任何阻止它工作的事情,所以如果在实践中它将预期的值存储在我的机器上的预期位置,我并不感到惊讶。
答案 1 :(得分:2)
做了一些标准检查。由于test
具有私有非静态成员,因此它不是POD。因此new test
默认初始化对象,new test()
对其进行初始化。正如其他人所指出的那样,值初始化将成员设置为零,这可能会让人感到意外。
默认初始化使用隐式定义的默认构造函数,它省略了成员a
和n
的初始值设定项。
12.6.2p4:对类
X
的构造函数的调用完成后,如果构造函数的 mem-initializers 中既未指定X
的成员,也不default-initialized,nor-initialized,也没有在构造函数体的执行过程中给出一个值,该成员具有不确定的值。
不是“它的内存在构造函数之前的值,通常是不确定的。”标准直接表示如果构造函数对它们没有任何作用,则成员具有不确定的值。
所以给定test* p = new test;
,p->a
和p->n
具有不确定的值,任何右值使用都会导致未定义的行为。
答案 2 :(得分:1)
C ++中对象的创建/销毁分为两个任务: 内存分配/解除分配 和 对象初始化/取消初始化 即可。内存分配/释放的完成取决于对象的存储类(自动,静态,动态),对象初始化/取消初始化是使用对象的类型的构造函数/析构函数完成的。
您可以通过提供自己的 构造函数 / 析构函数 来自定义对象初始化/取消初始化。您可以通过重载 operator new
和 operator delete
<来自定义动态分配对象的分配/ em> 此类型。您可以为单个对象和数组(以及任意数量的其他重载)提供这些运算符的不同版本。
如果您想微调特定类型对象的构造/销毁,首先需要 决定 是否要进行分配/取消分配(动态分配的对象)或初始化/取消初始化。你的代码混合了两者,违反了C ++最基本的设计原则之一,所有已建立的实践,这个星球上每个已知的C ++编码标准,以及你的同事的假设。
答案 3 :(得分:1)
你的教授完全误解了operator new
的目的,void*
的唯一任务是分配尽可能多的内存并将new test();
返回给它。
之后调用构造函数来初始化该内存位置的对象。这不是程序员要避免的。
由于该类没有用户定义的构造函数,因此字段应该是未初始化的,在这种情况下,编译器可能会自由地将它们初始化为某个魔术值,以帮助查找未初始化值的使用(例如,用于调试版本)。这将破坏重载运营商所做的额外工作。
浪费额外工作的另一种情况是使用值初始化:{{1}}
答案 4 :(得分:1)
这是非常糟糕的代码,因为它需要初始化代码,它应该是构造函数的一部分,并将它放在operator new
中,它应该只分配新的内存。
表达式new test
可能泄漏内存(由p->a = new int[p->n];
分配)并且表达式new test()
肯定会泄漏内存。标准中没有任何内容阻止实现归零或设置为备用值,即使后续初始化通常不会触及内存,在使用对象初始化该内存之前由自定义operator new
返回的内存再次。如果test
对象值初始化,则保证泄漏。
也没有简单的方法来正确释放用new test
分配的测试。没有匹配的operator delete
,因此表达式delete t;
将在分配有operator delete
的内存上调用全局malloc
错误的内容。
答案 5 :(得分:0)
您的教授代码将无法在3/4的情况下正确初始化。
$ ./a.exe
Using Test::new
Using Test::new
A Count( 0) // zero initialized: pointer leaked.
A Pointer(0)
B Count( 10) // Works as expected because of default init.
B Pointer(0xd20388)
C Count( 1628884611) // Uninitialized as new not used.
C Pointer(0x611f0108)
D Count( 0) // Zero initialized because it is global (static storage duration)
D Pointer(0)
#include <new>
#include <iostream>
#include <stdlib.h>
class test
{
// code
int *a;
int n;
public:
void* operator new(size_t);
// Added dredded getter so we can print the values. (Quick Hack).
int* getA() const { return a;}
int getN() const { return n;}
};
void* test::operator new(size_t size)
{
std::cout << "Using Test::new\n";
test *p;
p=(test*)malloc(size);
p->n = 10; // Fixed size for simple test.
p->a = new int[p->n];
return p;
}
// Objects that have static storage duration are zero initialized.
// So here 'a' and 'n' will be set to 0
test d;
int main()
{
// Here a is zero initialized. Resulting in a and n being reset to 0
// Thus you have memory leaks as the reset happens after new has completed.
test* a = new test();
// Here b is default initialized.
// So the POD values are undefined (so the results are what you prof expects).
// But the standard does not gurantee this (though it will usually work because
// of the it should work as a side effect of the 'zero cost principle`)
test* b = new test;
// Here is a normal object.
// New is not called so its members are random.
test c;
// Print out values
std::cout << "A Count( " << a->getN() << ")\n";
std::cout << "A Pointer(" << a->getA() << ")\n";
std::cout << "B Count( " << b->getN() << ")\n";
std::cout << "B Pointer(" << b->getA() << ")\n";
std::cout << "C Count( " << c.getN() << ")\n";
std::cout << "C Pointer(" << c.getA() << ")\n";
std::cout << "D Count( " << d.getN() << ")\n";
std::cout << "D Pointer(" << d.getA() << ")\n";
}
class test
{
// code
int n;
int a[1]; // Notice the zero sized array.
// The new will allocate enough memory for n locations.
public:
void* operator new(size_t);
// Added dredded getter so we can print the values. (Quick Hack).
int* getA() const { return a;}
int getN() const { return n;}
};
void* test::operator new(size_t size)
{
std::cout << "Using Test::new\n";
int tmp;
std::cout << How big?\n";
std::cin >> tmp;
// This is a half arsed trick from the C days.
// It should probably still work.
// Note: This may be what the professor should have wrote (if he was using C)
// This is totally horrible and whould not be used.
// std::vector is a much:much:much better solution.
// If anybody tries to convince you that an array is faster than a vector
// The please read the linked question below where that myth is nailed into
// its over sized coffin.
test *p =(test*)malloc(size + sizeof(int) * tmp);
p->n = tmp;
// p->a = You can now overflow a upto n places.
return p;
}
答案 6 :(得分:-2)
如你所示,这是错误的。你也可以看出错误是多么容易。
除非您尝试管理自己的内存分配,否则通常没有任何理由,在C ++环境中,您最好不要学习STL并编写自定义分配器。