假设MyClass
使用默认的析构函数(或没有析构函数),并使用以下代码:
MyClass *buffer = new MyClass[i];
// Construct N objects using placement new
for(size_t i = 0; i < N; i++){
buffer[i].~MyClass();
}
delete[] buffer;
是否有任何优化器可以删除此循环?
此外,我的代码有没有办法检测MyClass
是否正在使用空/默认构造函数?
答案 0 :(得分:3)
调用析构函数的正确语法是
template<typename T>
void destruct(T& x)
{
x.~T(); // call destructor on x
}
// The below is valid even though ints do not have destructors
int x;
destruct(x);
该语法对于像int这样的类型有效(当作为模板参数传递时)但是是无操作(什么都不做),所以模板代码(如std::vector<T>
中的模板代码调用其内容的析构函数)是有效的。
IMO编译器应该直截了当地看到循环内容包含无操作,因此整个循环本身没有副作用,因此删除整个循环。现代编译器具有非常复杂的优化器,并且应该能够删除无效的代码。如果编译器没有删除冗余循环,它将在vector<int>
的析构函数中发出冗余代码!没有代码可以为int的析构函数发出,所以只有一个空循环迭代遍历不执行任何操作的元素。我相信任何理智的优化者都会删除整个循环。
当然,如果你在一个确实在析构函数中工作的类上调用析构函数,那么它仍然必须被调用,并且仍然会有一个循环(需要进行其他相关的优化,例如展开)。
另一个基于副作用的优化示例是这样的代码:
for (int i = 0; i < 1000000; ++i)
; // just count up i, no statement (same as no-op)
cout << i;
可能会被优化以简单地打印常量1000000而不进行处理,因为编译器足够聪明,可以知道i
的总体副作用变为百万并被打印。这是优化者所做的一些令人印象深刻的事情的基础知识,所以不要担心,它会做得很好。如果您一直很好奇,请检查优化版本中的输出程序集,看看实际发生了什么。
答案 1 :(得分:3)
此代码存在一些问题。
首先,您不需要调用析构函数。 MyClass buffer* = new MyClass[i]; delete[] buffer;
做得很好。 (注意,不是数组语法。)
那就是说,你评论让我相信你的意思是其他的东西,比如:
// vector, because raw memory allocation is bad
std::vector<char> memory(sizeof(MyClass) * count);
std::vector<MyClass*> objs; objs.reserve(count);
for (size_t i = 0; i < count; ++i)
objs.push_back(new (memory[sizeof(MyClass) * i]) MyClass()); // place it
然后:
for (size_t i = 0; i < count; ++i)
objs[i].~MyClass(); // destruct (note syntax)
当然没有必要删除任何内容,因为我们使用了一个向量。这是调用析构函数的正确语法。
会优化吗?这取决于编译器可以确定析构函数是否什么都不做。如果析构函数是编译器生成的,我相信它会删除无用的循环。如果析构函数是用户定义的但是在标题中,它也能够看到它什么都不做并删除循环。
但是,如果它在某个其他目标文件中,我认为它不会,即使它是空的。这取决于编译器在链接阶段进行优化的能力。最好的方法是查看生成的程序集。
答案 2 :(得分:0)
您不像上面那样创建动态数组。你这样做:
MyClass* buffer = new MyClass[i];
除了你上面的循环外,它也不会调用析构函数。如果类有一个重载的“〜”运算符,那么它将调用该代码。
所以没有...没有编译器会优化该循环。代码也不太可能编译。