C++11,返回向量函数风格的移动语义

C++11, move semantics of returning vectors functional style

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

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

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 函数后却与原始内存位置相同?

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

我不太清楚为什么调用后第一个元素的内存地址是一样的abs(),但是有更好的写法。如果使用 std::transform(),代码会更简单,可在 algorithm header:

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 函数。该函数的 return 值将被插入到 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;
});

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;
}

可以简化为(你使用的是引用(&))为

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);

根据安迪的建议,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.: 对不起我的英语不好

显然,当您从不能移动的常量引用创建新副本时。如果您想以向量移入和移出它们的方式编写这些函数,则需要按值(此处应首选)或右值引用传递向量,并将向量移入函数。

#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());
}