【问题标题】:Clang modifies return value in destructor?Clang在析构函数中修改返回值?
【发布时间】:2019-02-07 01:49:33
【问题描述】:

在尝试编写一个类来计算调用它的构造函数和析构函数之间的持续时间时,我遇到了我认为是 clang 中的一个错误。 (编辑:这不是错误;它是实现定义的复制省略)

下面的timer 结构保留了一个指向作为引用传入的持续时间对象的指针,并将范围的持续时间添加到此。

#include <iostream>
#include <chrono>
struct timer {
    using clock      = std::chrono::high_resolution_clock;
    using time_point = clock::time_point;
    using duration   = clock::duration;
    duration* d_;
    time_point start_;
    timer(duration &d) : d_(&d), start_(clock::now()) {}
    ~timer(){
        auto duration = clock::now() - start_;
        *d_ += duration;
        std::cerr << "duration: " << duration.count() << std::endl;
    }
};

timer::duration f(){
    timer::duration d{};
    timer _(d);
    std::cerr << "some heavy calculation here" << std::endl;
    return d;
}

int main(){
    std::cout << "function: " << f().count() << std::endl;
}

使用 clang 7.0.0 编译时,输出为:

some heavy calculation here
duration: 21642
function: 21642

而对于 g++ 8,输出是

some heavy calculation here
duration: 89747
function: 0

在这种情况下,我确实喜欢 clangs 行为,但从我在其他地方找到的结果来看,应该在运行析构函数之前复制返回值。

这是 Clang 的错误吗?还是这取决于(实现定义?)返回值优化?

无论timer 中的duration d 是指针还是引用,行为都是相同的。

--

我确实意识到编译器不一致可以通过更改f 来解决,以便计时器的范围在返回之前结束,但这不是重点。

timer::duration f(){
    timer::duration d{};
    {
        timer _(d);
        std::cerr << "some heavy calculation here" << std::endl;
    }
    return d;
}

【问题讨论】:

  • 仅供参考,您的命名约定使您的代码难以阅读。请尽量不要为同一范围内的不同事物回收相同的标识符。
  • 我想知道这是否与 NRVO 有关...您正在编译哪个 c++ 标准版本?
  • @Frank 对于 g++,c++11 和 c++17 从函数返回 0,而 clang 对于 c++11 和 c++17 返回非零。
  • 调试 gcc 的输出,似乎 gcc 在运行析构函数之前为返回值构造了一个副本。 d 的副本在析构函数运行之前被构造。检查析构函数的thisthis 返回的f(),它们是不同的对象。我在复制省略上解析 cppreference.com 的条目,表明在这种情况下它是可选的,不是必需的,因此是实现定义的。 clang 显然省略了副本,而 gcc 没有;但我对自己的解释还不够确定,无法将其作为答案。
  • @SamVarshavchik 我认为你是对的。 stmt.return 表示返回值的复制构造在局部变量的析构函数之前排序。可能发生 clang 输出的唯一方法是省略副本并且 df 的返回值是同一个对象。

标签: c++ clang++


【解决方案1】:

简短回答:由于 NRVO,程序的输出可能是0 或实际持续时间。两者都有效。


背景请先看:

指南:

  • 避免使用修改返回值的析构函数。

例如,当我们看到以下模式时:

T f() {
    T ret;
    A a(ret);   // or similar
    return ret;
}

我们需要问自己:A::~A() 是否会以某种方式修改我们的返回值?如果是,那么我们的程序很可能有错误。

例如:

  • 在销毁时打印返回值的类型就可以了。
  • 在销毁时计算返回值的类型不好

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-12-01
    • 2014-04-21
    • 2016-09-21
    • 1970-01-01
    • 1970-01-01
    • 2018-11-25
    • 1970-01-01
    • 2017-12-14
    相关资源
    最近更新 更多