【问题标题】:C++11, move semantics of returning vectors functional styleC++11,移动返回向量函数式语义
【发布时间】:2016-11-11 00:39:37
【问题描述】:

我正在创建一个示例来尝试了解在使用 c++11 时如何优化移动语义。

首先,我有以下两个函数,它们接受一些输入并返回输入的修改版本。

std::vector<float> abs(const std::vector<float> &v)
{
  std::vector<float> abs_vec = v;

  for (auto &val: abs_vec) {
    auto i = &val - &abs_vec[0];
    abs_vec[i] = std::abs(abs_vec[i]);
  }

  return abs_vec;
}

std::vector<float> normalize(const std::vector<float> &v)
{
  std::vector<float> norm_vec = v;

  auto max_val = std::max_element(std::begin(norm_vec), std::end(norm_vec));
  auto min_val = std::min_element(std::begin(norm_vec), std::end(norm_vec));
  auto norm_factor = *max_val;
  auto min_check = std::abs(*min_val);

  if (min_check > std::abs(norm_factor)) {
    norm_factor = min_check;
  }

  for (auto &val: norm_vec) {
    auto i = &val - &norm_vec[0];
    norm_vec[i] = norm_vec[i] / norm_factor;
  }

  return norm_vec;
}

然后我用下面的代码测试函数:

int main()
{
  std::vector<float> tmp{-1,2,-3,4,5};

  printf("pointer before: %p\n", &tmp[0]);
  tmp = normalize(tmp);
  printf("pointer after normalize: %p\n", &tmp[0]);
  tmp = abs(tmp);
  printf("pointer after abs: %p\n", &tmp[0]);

};

从这里我得到输出:

pointer before: 0x156dc20
pointer after normalize: 0x156e050
pointer after abs: 0x156dc20

谁能解释一下发生了什么以及为什么我在标准化后得到了一个副本,但在 abs 函数调用之后却与原始的内存位置相同?

有没有更好的方法以这样的函数式风格编写函数,从而产生更优化的代码?

【问题讨论】:

    标签: c++ c++11 functional-programming move-semantics


    【解决方案1】:

    不知道为什么调用abs()后第一个元素的内存地址是一样的,但是有更好的写法。如果您使用std::transform(),代码会更简单,可在algorithm 标头中找到:

    std::vector<float> abs(const std::vector<float> &v)
    {
        std::vector<float> abs_vec;
    
        std::transform(v.begin(), v.end(), std::back_inserter(abs_vec),
                       [] (float val) {
            return std::abs(val);
        });
    
        return abs_vec;
    }
    

    这将遍历v 中的所有元素,并为每个元素调用 lambda 函数。该函数的返回值将插入到abs_vec 的末尾。

    normalize() 末尾的循环也可以重写为使用std::transform()。在这种情况下,可以捕获 norm_factor 变量,以便在 lambda 中使用它:

    std::transform(v.begin(), v.end(), std::back_inserter(norm_vec),
                   [norm_factor] (float val) {
        return val / norm_factor;
    });
    

    【讨论】:

    • 这是否会自动调用内存移动,因为我将它返回到与输入相同的变量?
    • 另外,对于第二个示例, norm_factor 是 lambda 函数的第二个参数。您能否举例说明如何实现?
    • @toeplitz 是的,当函数返回时,向量将被移动而不是复制。 C++11 指出,如果一个类型有一个移动构造函数(std::vector 有),则可以省略副本并返回实际值而不是调用副本。
    【解决方案2】:

    当你从一个不能移动的 const-reference 创建一个新副本时,显然。如果您想以向量移入和移出的方式编写这些函数,则需要按值(此处应首选)或右值引用来传输向量,然后将向量移入函数中。

    #include <cstdio>
    #include <vector>
    
    std::vector<float> plus_five(std::vector<float> v)
    {
        for (auto& e : v)
            e += 5;
    
        return v;
    }
    
    int main()
    {
        std::vector<float> v{1, 2, 3, 4};
    
        std::printf("%p\n", v.data());
        v = plus_five(std::move(v)); // move
        std::printf("%p\n", v.data());
        v = plus_five(v); // copy
        std::printf("%p\n", v.data());
    }
    

    【讨论】:

    • 为了尝试使用更函数化的编程风格,对于这些类型的函数,这是推荐的方法吗?假设向量非常大,与不使用移动并坚持使用 const 和复制相比,这是否值得优化?
    • @toeplitz:是的,这是推荐的方式。这个答案避免了 all 副本(除非它明确复制,如评论所述)。如果您在示例中使用这种样式,您每次都会得到相同的地址。
    【解决方案3】:

    printf的值是vector底层数据成员的地址。

    所以我想,在你的情况下

    1)tmp的创建在0x156dc20位置创建(分配)一个数据成员

    2)normalize()操作将数据成员替换为normalize()(norm_vec)中创建的向量的数据成员,地址为0x156e050,并释放前一个数据成员的内存

    3)abs()操作用abs()abs_vec)创建的vector的数据成员替换数据成员,也就是0x156dc20,回收之前释放的内存

    简单地说:我假设abs_vect回收了tmp之前使用的内存

    顺便说一句,您的 for-each 循环过于复杂。

    循环

    for (auto &val: norm_vec) {
      auto i = &val - &norm_vec[0];
      norm_vec[i] = norm_vec[i] / norm_factor;
    }
    

    可以简化为(您使用的是参考文献 (&amp;))

    for (auto &val: norm_vec)
      val /= norm_factor;
    

    和循环

    for (auto &val: abs_vec) {
      auto i = &val - &abs_vec[0];
      abs_vec[i] = std::abs(abs_vec[i]);
    }
    

    可以简化(相同的原因)为

    for (auto &val: abs_vec)
      val = std::abs(val);
    

    按照 Andy 的建议,使用 std::transform 效果更好(恕我直言)。

    关于norm_factor的发现,我认为

    auto pairIt = std::minmax_element(norm_vec.begin(), norm_vec.end());
    auto norm_factor = std::max(std::abs(*(pairIt.first)), *(pairIt.second));
    

    更简单(我想也更优化)
    auto max_val = std::max_element(std::begin(norm_vec), std::end(norm_vec));
    auto min_val = std::min_element(std::begin(norm_vec), std::end(norm_vec));
    auto norm_factor = *max_val;
    auto min_check = std::abs(*min_val);
    
    if (min_check > std::abs(norm_factor)) {
      norm_factor = min_check;
    }
    

    p.s.:对不起我的英语不好

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-24
      • 1970-01-01
      • 2011-09-06
      • 2014-08-16
      • 1970-01-01
      相关资源
      最近更新 更多