微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

NRVO 是否发生在静态成员变量初始化中?

如何解决NRVO 是否发生在静态成员变量初始化中?

我有一个带有大型静态 std::array 的类,需要进行昂贵的计算才能初始化,所以我定义了一个静态方法来执行它。然而,我不知道是否发生了一些复制,或者计算是否在成员数组内执行。

class A
{
  static inline array<double,100000> a = fill_a();
  static array<double,100000> fill_a() 
  {
    array<double,100000> b;  
    /* large computation involving b with non constexpr functions */
    return b;
  }
};

如何测试 b 是否真的被复制了?或者如果一切都在一个内部完成? 感谢您提供任何帮助或提示

解决方法

您将需要在此处测试您的编译器,因为并非所有编译器都会忽略该副本,并且它们是否会忽略该副本可能取决于优化设置。

有两种方法可以做到这一点,我建议在优化的构建中同时使用它们。

  1. 检查生成的汇编代码,看看大数组是在静态存储中构造的,还是先作为堆栈临时然后复制。

  2. 将数组包装在一个其复制构造函数包含日志的类中,使用该日志类的对象而不是数组,并检查是否有任何副本。

#2 的测试时间不应超过 5 分钟。 #1 更复杂,但如果您正在从事对性能敏感的工作,那么这是一项很有价值的技能。

这是我对 #2 的设想:

#include<array>
#include<algorithm>
using namespace std;

struct S
{
    array<double,100000> a;
    S() { puts("ctor"); }
    S(S const& rhs) : a(rhs.a) { puts("copy ctor"); }
    S(S const&& rhs) : a(rhs.a) { puts("move ctor"); }
};

class A
{
  static S fill_a() 
  {
    S b;  
    /* large computation involving b with non constexpr functions */
    return b;
  }
  inline static S a = fill_a();
};

int main()
{
}
,

我不知道“NRVO”是什么。从上下文我猜测你指的是复制省略。在少数情况下允许复制省略。特别是,当返回与函数返回类型相同类型的非 volatile 局部变量时,允许复制省略(参见 [class.copy.elision] 第 1.1 段。 >

使用 C++17 复制省略在某些情况下成为强制性的。然而,在所有这些情况下,省略的副本将复制一个 prvalue,这显然不是命名变量的情况。因此,编译器可以删除该副本,但它不是强制这样做的。这是一个实施资格问题。

由于复制省略的效果是所讨论的对象是在目标位置构造的并且目标位置是已知的(在这种情况下为 &S::b),因此在以下情况下应该可以assert()不使用复制省略,例如:

#include <algorithm>
#include <cassert>
#include <array>

struct S
{   
    static std::array<double,10> fill_a() {
        std::array<double,10> b;
        assert(&a == &b);
        std::fill(b.begin(),b.end(),0); 
        return b;
    }   
    static inline std::array<double,10> a = fill_a();
};  

int main()
{   
    return S::a[0]; // reference S::a
}

有趣的是,上面的代码做了 assert(),虽然我认为 g++ 通常在这些情况下实现了复制省略。不幸的是,很可能添加 assert(&a == &b),或者更准确地说是获取返回对象的地址,从而抑制复制省略。

否则,有必要观察复制的副作用以确定复制省略没有发生。使用 std::array<double,N> 没有任何可观察到的副作用。但是,可以使用自定义类,例如:

#include <algorithm>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <array>

struct C {
    int i = 0;
    C() = default;
    C(C &&){ std::cout << "moved\n"; };
    C(C const&){ std::cout << "copied\n"; };
};
struct S
{
    static std::array<C,10> fill_a() {
        std::array<C,10> b;
        c = &b;
        // assert(&a == &b);
        return b;
    }
    static inline std::array<C,10>  a = fill_a();
    static inline std::array<C,10>* c = nullptr;
};

int main()
{
    assert(S::c == &S::a);
    return S::a[0].i; // reference S::a
}

这不会打印任何关于复制或移动 C 对象的内容,即使获取了局部变量的地址,甚至 assert() 中的 main() 保持不变。但是,当取消注释 fill_a() 中的断言时,它会触发!可以通过某种使复制省略不可能的方式来验证副作用会做正确的事情,例如,使用

std::array<C,10> fill_a() {
    std::array<C,10> b,d;
    return std::rand() % 2? b: d;
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。