为什么不删除函数参数的副本

时间:2018-07-27 02:49:04

标签: c++ g++ c++17

我有以下代码

#include <cstdlib>
#include <vector>
#include <chrono>
#include <iostream>

static const uint64_t BENCHMARK_RUNS(1000000);

std::vector<float> vec_mul_no_ref(const std::vector<float> x,
                                  const std::vector<float> y) {
  if(x.size() != y.size()) {
    throw std::runtime_error("vectors are not the same size!");
  }
  std::vector<float> ans(x.size());
  for (size_t ii=0; ii<x.size(); ii++) {
    ans[ii] = x[ii] * y[ii];
  }
  return ans;
}

std::vector<float> vec_mul_ref_args(const std::vector<float>& x,
                                   const std::vector<float>& y) {
  if(x.size() != y.size()) {
    throw std::runtime_error("vectors are not the same size!");
  }
  std::vector<float> ans(x.size());
  for (size_t ii=0; ii<x.size(); ii++) {
    ans[ii] = x[ii] * y[ii];
  }
  return ans;
}

void vec_mul_all_ref(const std::vector<float>& x,
                   const std::vector<float>& y, std::vector<float>& ans) {
  if(x.size() != y.size() || y.size() != ans.size()) {
    throw std::runtime_error("vectors are not the same size!");
  }
  for (size_t ii=0; ii<x.size(); ii++) {
    ans[ii] = x[ii] * y[ii];
  }
}

void bench_vec_mul() {
  size_t vec_size(10000);
  std::vector<float> x(vec_size);
  std::vector<float> y(vec_size);
  for(size_t ii=0; ii<vec_size; ii++) {
    x[ii] = (static_cast<double>(rand()) / RAND_MAX) * 100.0;
    y[ii] = (static_cast<double>(rand()) / RAND_MAX) * 100.0;
  }
  // bench no_ref
  auto start = std::chrono::steady_clock::now();
  for (uint64_t ii=0; ii < BENCHMARK_RUNS; ii++) {
    std::vector<float> ans = vec_mul_no_ref(x, y);
  }
  auto end = std::chrono::steady_clock::now();
  double time = static_cast<double>(
                std::chrono::duration_cast<
                std::chrono::microseconds>(end-start).count());
  std::cout << "Time to multiply vectors (no_ref) = " 
            << time / BENCHMARK_RUNS * 1e3 << " ns" << std::endl;

  // bench ref_args
  start = std::chrono::steady_clock::now();
  for (uint64_t ii=0; ii < BENCHMARK_RUNS; ii++) {
    std::vector<float> ans = vec_mul_ref_args(x, y);
  }
  end = std::chrono::steady_clock::now();
  time = static_cast<double>(
                std::chrono::duration_cast<
                std::chrono::microseconds>(end-start).count());
  std::cout << "Time to multiply vectors (ref_args) = " 
            << time / BENCHMARK_RUNS * 1e3 << " ns" << std::endl;

  // bench all_ref
  start = std::chrono::steady_clock::now();
  for (uint64_t ii=0; ii < BENCHMARK_RUNS; ii++) {
    std::vector<float> ans(x.size());
    vec_mul_all_ref(x, y, ans);
  }
  end = std::chrono::steady_clock::now();
  time = static_cast<double>(
                std::chrono::duration_cast<
                std::chrono::microseconds>(end-start).count());
  std::cout << "Time to multiply vectors (all_ref) = " 
            << time / BENCHMARK_RUNS * 1e3 << " ns" << std::endl;
}

int main() {
  bench_vec_mul();
  return 0;
}

在我的笔记本电脑上(与g++ -o benchmark main.cc -std=c++17 -O3编译后,此代码的示例输出是:

Time to multiply vectors (no_ref) = 5117.05 ns                                      
Time to multiply vectors (ref_args) = 3000.69 ns
Time to multiply vectors (all_ref) = 2996.84 ns

第二次和第三次如此相似,表示正在执行返回值优化,并且删除了副本。我的问题是:为什么编译器也没有删除函数参数的副本,以便第一次与第二次匹配?将函数参数声明为const可以确保它们不会更改,因此不会意外地编辑原始变量。

我有gcc(GCC)8.1.1 20180531

2 个答案:

答案 0 :(得分:5)

  

我的问题是:为什么编译器也没有删除函数参数的副本,以便第一次匹配后两个?

因为标准不允许他们这样做。

淘汰不是刚刚发生的事情;它不是普遍存在的假设规则的一部分。因为它影响用户可见的行为(复制/移动构造函数可能是用户定义的代码),所以该标准必须明确指出,有时实现可以自由地不调用它们。因此,该标准只允许在非常特殊的情况下省略

从参数表达式初始化参数时,仅当参数为值类型且参数表达式为该类型的临时类型时才允许省略。好吧,如果您想获取技术知识,在C ++ 17中不会出现这种情况:prvalue可以直接初始化参数而不是显示临时变量,因此无需复制/移动即可。

但事实并非如此。 xy不是prvalue,因此不适合省略。它们将被复制到这些参数中。

事实上,命名对象唯一可以进行省略的条件是您返回。即便如此,如果它是该函数的参数,它也不起作用。

答案 1 :(得分:2)

Nicol解释了您的情况下标准不允许复制省略的技术原因。

关于基本原理,我认为只是值传递具有与引用传递不同的生存期和所有权语义。当一个函数通过值接收一个参数时,它不仅是在说“我想要一个副本,以便可以对其进行修改并使原始对象不受影响”。也是在说:“我想拥有这个对象(我的副本),并控制它的生存期,以使它在返回之前不会被破坏。”

在您的程序中,vec_mul_no_refvec_mul_ref_args之间没有真正的区别。但是,想象其他人对这些功能的调用有所不同。特别是,假设我调用了vec_mul_ref_args(x, y),并且以某种方式xy在另一个线程中被销毁了。这将是一场数据竞赛。但是,如果我叫vec_mul_no_ref,那就没问题了。