【问题标题】:Will unused destructor be optimized out?未使用的析构函数会被优化掉吗?
【发布时间】:2010-04-14 01:11:47
【问题描述】:

假设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 是否使用空/默认构造函数?

编辑:对不起我可怕的代码。我认为现在这是正确的..

【问题讨论】:

  • “析构函数”,而不是“解构函数”。附言我从未听说过会删除包含函数调用的循环的优化器。
  • 我认为大多数优化器会删除没有副作用的代码。
  • ~buffer[i]; 只执行 1 的补码,然后将结果丢弃。
  • @Mike DeSimone:像这样的激进优化器几十年前就开始搞乱基准程序。不幸的是,我没有想出一个日期,但我确定我是把它作为 1980 年左右的历史事件来阅读的。

标签: c++ optimization g++


【解决方案1】:

调用析构函数的正确语法是

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&lt;T&gt; 这样的模板代码在其内容上调用析构函数是有效的。

IMO 编译器应该很容易看到循环内容包含无操作,因此整个循环本身没有副作用,因此删除整个循环。现代编译器具有非常复杂的优化器,并且应该能够删除没有效果的代码。如果编译器没有删除冗余循环,它将在 vector&lt;int&gt;! 的析构函数中发出冗余代码!没有为 int 的析构函数发出代码,所以只会有一个空循环遍历元素,什么都不做。我确信任何理智的优化器都会删除整个循环。

当然,如果您在一个确实在其析构函数中工作的类上调用析构函数,则仍必须调用该析构函数并且仍然会有一个循环(取决于其他相关的优化,例如展开)。

另一个基于副作用的优化的简单示例是这样的代码:

for (int i = 0; i < 1000000; ++i)
    ;  // just count up i, no statement (same as no-op)

cout << i;

可能会被优化为简单地打印常量 1000000 而无需处理,因为编译器足够聪明,知道整体副作用是 i 变成一百万并被打印。这是优化器所做的一些令人印象深刻的事情的基础,所以不要担心,它会做得很好。如果您好奇,请检查优化构建中的输出程序集以了解实际情况。

【讨论】:

  • x.~int(); // valid even though an int! 我不认为这是有效的。
  • 你说得对,我无法让语句 x.~int() 直接编译。但它确实通过模板函数编译。我已经更改了示例来证明这一点。
【解决方案2】:

这段代码有一些问题。

首先,您不需要调用析构函数。 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)

当然没有必要删除任何东西,因为我们使用了向量。这是调用析构函数的正确语法。

会优化吗?这取决于编译器可以确定析构函数是否什么都不做。如果析构函数是编译器生成的,我相信它会删除毫无价值的循环。如果析构函数是用户定义的,但在标头中,它也可以看到它什么都不做并删除循环。

但是,如果它在其他目标文件中,我认为它不会,即使它是空的。这取决于您的编译器在链接阶段进行优化的能力。最好的了解方法是查看生成的程序集。

【讨论】:

  • 是的,你对我的意图是正确的。在发布它之前我没有尝试编译它:\所以听起来它会优化它,只要它可以告诉它这样做是安全的。我认为这意味着最好不要定义析构函数,因为它可以只查看标题并知道它什么都不做?
  • 另外澄清一下,析构函数不会删除任何东西,我主要想知道池分配器(你不需要做任何内存管理,但你可能仍然希望调用析构函数)。
  • @Brendan:对。如果您不需要定义事物,就永远不是一个好主意,这只是浪费时间和编译器的更多工作。
  • @Brendan:如果你提前知道析构函数不会包含任何东西,那么为什么不在你的内存池代码中直接调用它呢?我假设您希望它是通用的,因此它可以与析构函数一起使用而无需...您始终可以对其进行测试: gcc -O3 -S ... 然后查看生成的程序集
  • 是的,我打算把它变成通用的,我认为这就是 Boost 的 object_pool 的工作方式,我想知道它是否有机会在其中被优化。
【解决方案3】:

您不会像上面那样创建动态数组。你可以这样做:

MyClass* buffer = new MyClass[i];

除了上面的循环之外,它也不会调用析构函数。如果该类有一个重载的“~”运算符,那么它将调用该代码。

所以不...没有编译器会优化该循环。该代码也极不可能编译。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-09-21
    • 2017-08-10
    • 1970-01-01
    • 2012-05-05
    • 1970-01-01
    • 2020-11-25
    • 2012-09-02
    • 2012-12-26
    相关资源
    最近更新 更多