【问题标题】:"Inline" static declaration of object with initializer list带有初始化列表的对象的“内联”静态声明
【发布时间】:2018-06-03 06:39:08
【问题描述】:

抱歉标题太尴尬了,但我找不到更好的。

考虑这个示例代码(除了说明问题之外没有其他目的):

#include <vector>

void FooBar(int);

void func1()
{
    static std::vector<int> vec {1, 2, 3, 4};

    for (auto & v : vec)
      FooBar(v);
}

void func2()
{
    for (auto & v : std::vector<int> {1, 2, 3, 4})
      FooBar(v);
}

这个的反汇编可以找到here

func1 中,静态vec 向量应该在启动时一劳永逸地构造。实际上上面提到的godbolt上的反汇编表明静态vec的初始化仅在func1的第一次调用时完成,而不是在启动时完成,但这不是重点。

现在考虑func2:这里的向量在for 语句中直接声明为“内联”(不确定实际是如何调用的),但当然每次调用func2 时都会构造该向量。

有没有办法在for 语句中静态声明该向量,例如for (auto &amp; v : static std::vector&lt;int&gt; { 1, 2, 3, 4}),不幸的是这不是合法的C++。

【问题讨论】:

  • 也许将其包装在自定义类中?也看看这个stackoverflow.com/questions/13816850/…
  • @JakeFreeman 但这会使代码比func1 中使用的方法更麻烦。
  • 除了好奇因素,第二种方法有什么好处吗?
  • 请注意,在这里使用std::vector 会适得其反,因为它的全部目的是运行时可调整大小,这直接违背了您对单例/常量的渴望。所以我假设这是一些用户类的占位符,其中constexpr 版本(std::arraystd::initializer_list)不容易获得。
  • @MichaelWalz 如果您不想打扰大小,通常可以调用make_array 或类似的函数。

标签: c++ c++11 vector


【解决方案1】:

这实际上对您没有帮助。但是你可以在 C++2a 中做一些事情。然而,它是基于 C++14 和 C++17 中已经存在的东西。

C++2a 在基于范围的 for 循环中添加了一个 init-statement。这是新的一点,旧的一点是它与今天定义的init-statement 相同。定义如下([stmt.stmt]):

init-statement:
    expression-statement
    simple-declaration

我要的是simple-declaration 可能包含static 限定符。是的,它符合您的预期。所以在 C++2a 中你可以这样写:

for (static std::vector<int> vec {1, 2, 3, 4}; int v : vec) {
   // Do things.
}

如果你今天想测试编译器对它的支持,这里有一个 C++17 组件:

if (static std::vector<int> vec {1, 2, 3, 4}; true) {
    // init-statement in if was added in C++17
  for(int v : vec)
    FooBar(v);
}

And its disassembly.

【讨论】:

  • 嗯,我不知道static 可以包含在这里(从来没有任何理由尝试)。 if 版本是否比仅仅打开一个新的块范围有什么好处?
  • @underscore_d - 什么都没有。这只是对语法产生的测试。证明静态对象的 init 语句是有效的。
【解决方案2】:

另一个使用 lambda 的选项(来自主要问题的 cmets):

void func()
{
    for(auto & v : ([]() -> std::vector<int>& { static std::vector<int> vec{1, 2, 3, 4}; return vec; })())
        FooBar(v);
}

【讨论】:

  • 是的。编译并运行:ideone 但它仍然看起来很吓人。 (我印象深刻 - 没有得到 operator() 的诀窍。)
  • 出于好奇:为什么它可以与operator() 一起使用,但没有它甚至无法编译?
  • @Scheff - 通过函数调用,它调用 lambda 并获取要迭代的向量。如果没有调用,它会尝试遍历 lambda 对象本身。
【解决方案3】:

不,在当前的 C++ (C++17) 中这是不可能的。基于范围的 for 等价于以下伪代码:

{
    auto && __range = range_expression ;
    auto __begin = begin_expr ;
    auto __end = end_expr ;
    for ( ; __begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
} 

这里没有init-statementrange_expression 必须先验地初始化,或者您必须传入要评估的 braced-init-list(如 std::vector&lt;int&gt;{1, 2, 3, 4})。这将在 C++20 中进行更改。

【讨论】:

    【解决方案4】:

    您可以做得更好:您可以将所有内容都放在堆栈上。如果您在 -O2 或更高版本上编译以下内容,它实际上会展开为对 FooBar() 的 4 次调用。

    另外,在关闭优化的情况下查看反汇编并不是很有意义。

    void func3()
    {
        constexpr int vs [] = { 1, 2, 3, 4 };
        for ( int const v : vs )
            FooBar(v);
    }
    

    【讨论】:

      【解决方案5】:

      确实,C++ 标准中没有任何内容不允许您正在寻找的优化。 std::vector&lt;int,std::allocator&gt; 分配的堆内存确实可以在不改变程序行为的情况下被静态内存替换。但正如您的链接所示(即使添加了积极的优化选项)编译器不会执行预期的优化。

      因此您可以选择使用std::array 而不是std::vectorstd::array 很容易被优化器“理解”,here 的汇编:

      void FooBar(int);
      
      void func2()
      {
        for (auto & v : std::array<int,4> {1, 2, 3, 4})
          FooBar(v);
      }
      

      正如您在程序集中看到的,数组存储在静态内存中:

      .LC0:
          .long   1
          .long   2
          .long   3
          .long   4
      

      为了好玩,您可以通过使用使用静态内存的自定义分配器来获得同样好的组装,here the assembly of:

      void FooBar(int i);
      
      template<class T>
      class static_alloc
        {
        static typename std::aligned_storage<4*sizeof(T),alignof(T)>::type buff;
        static bool allocated;
      
      public:
      
        using value_type = T;
      
        bool operator==(const static_alloc&){
          return true;
        }
        bool operator!=(const static_alloc&){
          return false;
        }
      
        T* allocate(std::size_t n)
          {
          if (allocated) throw std::bad_alloc{};
          allocated=true;
          return reinterpret_cast<T*>(&buff);
          }
        void deallocate(T*,std::size_t)
          {
          allocated=false;
          }
        };
      template<class T>
      bool static_alloc<T>::allocated=false;
      template<class T>
      std::aligned_storage_t<4*sizeof(T),alignof(T)> static_alloc<T>::buff;
      
      
      void func2()
      {
         for (auto & v : std::vector<int,static_alloc<int>>{1,2,3,4})
            FooBar(v);
      }
      

      【讨论】:

      • 为了自动进行有问题的优化,编译器必须认识到即使程序对每个元素都采用非const 左值引用,这些元素也不会被写入。不是不可能,但肯定比使用autoconst auto&amp; 而不是auto&amp; 的版本更难。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-10
      • 2020-05-18
      • 1970-01-01
      • 2010-09-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多