为什么没有为函数参数删除副本
Why is the copy not being elided for function arguments
我有以下代码
#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
我的问题是:为什么编译器不删除函数参数的副本,以便第一次与第二次匹配?
因为标准不允许它们是.
Elision并不是一件刚刚发生的事情;它不是无处不在的"好像"规则的一部分。因为它会影响用户可见的行为(复制/移动构造函数可能是用户定义的代码(,所以标准必须明确指出,有时,实现可以自由地不调用它们。因此,该标准仅允许在特定情况下省略。
当从参数表达式初始化参数时,只有当参数是值类型并且参数表达式是该类型的临时参数时,才允许省略。如果你想了解技术,在C++17中,这种情况下没有省略:prvalue将直接初始化参数,而不是显示临时参数,因此没有复制/移动到elide。
但这里的情况并非如此。x
和y
不是prvalue,因此不符合省略条件。它们将被复制到这些参数中。
事实上,命名对象唯一有资格省略的时候是如果返回。即使这样,如果它是该函数的参数,它也不起作用。
Nicol解释了在您的案例中标准不允许复制省略的技术原因。
至于理由,我认为只是传递值与传递引用具有不同的生存期和所有权语义。当函数按值接收参数时,它不仅仅是说"我想要一个副本,这样我就可以修改它,并使原始副本不受影响"。它还说,"我想拥有这个对象(我的副本(,并控制它的生存期,这样它就不会在我返回之前被破坏。">
在您的程序中,vec_mul_no_ref
和vec_mul_ref_args
之间没有真正的区别。但想象一下,其他人对这些函数的称呼不同。特别是,假设我调用了vec_mul_ref_args(x, y)
,但不知何故,x
和y
在另一个线程的调用中途被破坏。这将是一场数据竞赛。但是,如果我打电话给vec_mul_no_ref
,那就好了。
- 是否有C++编译器选项允许激进地删除所有函数调用,并将参数传递给具有空体的函数
- 如何在不强制转换每个参数的情况下删除初始值设定项列表中从 int 到 char 的缩小转换?
- std::p ackaged_task 应该删除带有 const 参数的复制 c'tor
- Qt5 - 如何将"QList<T> *"指针作为信号参数传递,并在完成后将其删除?
- 编译器在C++中调用另一个函数时,在参数中查找已删除的构造函数
- 从函数参数包中删除最后一项
- 为什么没有为函数参数删除副本
- 删除 Netbeans 中的标准运行参数
- 文档在哪里说明如果参数不是从末端删除参数,则无法从QT信号插槽连接中删除参数?
- 是否允许删除以修改其参数
- 如何通过对象参数从指针矢量中删除对象和指针
- 简化可变参数模板:删除一些专用项
- 为什么删除默认参数会破坏此 constexpr 计数器?
- 使用自定义参数的过载删除操作员
- 如何查找/删除具有特定参数的结构向量的元素
- 带大小参数和不带大小参数的"运算符删除":当两者都可用时,选择哪一个?
- 如何从可变参数模板参数中删除元素
- 打开模板参数:GCC 是否删除开关
- 递归模板参数删除
- 基于模板参数删除成员