C ++ 11,移动返回向量功能样式的语义

时间:2016-07-08 12:30:52

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

我创建了一个示例,试图了解在使用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函数调用之后它的内存位置与原来相同?

有没有更好的方法来编写这样的功能样式的函数,这将导致更优化的代码?

3 个答案:

答案 0 :(得分:3)

我不确定为什么调用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;
});

答案 1 :(得分:3)

当你从一个不能移动的const-reference创建一个新副本时,显然。如果要以向量移入和移出向量的方式编写这些函数,则需要按值(这里应该首选)或rvalue-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());
}

答案 2 :(得分:2)

printf的值是向量的基础数据成员的地址。

所以我认为,在你的情况下

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

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

3)abs()操作将数据成员替换为vectorabs())中创建的abs_vec的数据成员,再次为0x156dc20,回收先前的释放内存

简单地说:我认为abs_vect回收了tmp

以前使用的内存

En passant,你的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);

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:抱歉我的英语不好