谁为没有通过const引用而付出代价

  • 本文关键字:引用 付出 代价 const c++
  • 更新时间 :
  • 英文 :


给定:

void foo(std::vector<int> v);
void bar()
{
    std::vector<int> v = ...; // many items
    foo(v);
}

在分析工具中,什么会显示为热路径?它是std::vector<T>的复制构造函数、运行时还是操作系统?我记得在学校里(我不是C++开发人员,只是和一些人一起工作),这将复制v,这可能需要时间。我知道这样的签名:

void foo(const std::vector<int>& v);

避免了这种可能代价高昂的复制操作。

按值复制std::vector<T>可能会做三件事:

  1. 内存管理器(C++运行时或自定义分配器)在可用内存块的存储中搜索新向量。如果它能找到它,那么它进入步骤#3
  2. 内存管理器(C++运行时或自定义分配器)从操作系统请求更多内存。这个调用本身相对便宜(与系统调用一样多),因为操作系统在延迟模式下提供内存——实际分配发生在对请求的VM页面的第一次写入时
  3. 编译器生成的代码(应用程序)为新向量中的每个元素替换new,如果T不是一般的可复制构造的。否则,调用memcpy()或其优化的矢量化(在sse意义上)对应物。就在第一次写入新存储器之前,如果它是从#2中的OS获取的,则OS将需要实际分配新的VM页面,并且这可能触发RAM访问(硬件)、TLB查找(硬件+OS)交换入/出(OS)

在您的特定示例中,T通常是可复制构造的,因此最坏的情况开销是C++运行时内存块查找+sbrk()系统调用+memcpy()调用。

决定按值还是按常量引用接受参数时,应考虑函数对该值的处理计划。如果函数只计划读取现有值,那么const引用是最有效的选择。如果被调用的函数在其实现中制作了引用对象的副本,那么传递值可能会更快,因为调用方可以在有意义的情况下移动构造参数,并消除对副本的需要。

void foo(std::vector<int> v);
void bar()
{
    std::vector<int> v = ...; // many items
    foo(std::move(v)); // no copy needed here
}

您可以期望调用std::vector<int>的复制构造函数。

如果一个聪明的编译器这样做绝对没有副作用,那么它可以优化值副本。此外,如果函数定义标记为inline,那么如果编译器满足您的请求(不必这样做),则不会进行值副本。

您最好的选择确实是通过const参考v。这不仅避免了值复制,而且意味着被调用的函数不能修改传递的参数。

编译器只需发出执行as-if的代码即可运行代码,也就是说,可观察的行为是相同的。

可能的热点应该是bar中的赋值运算符,因为您需要创建原始向量或复制构造函数。

在您的示例中,编译器可以用不同的方式重写它,如果它能推断出v再也没有在bar中使用过,那么它可能会将调用foo(v);实现为foo(std::move(v))(就像前面提到的@mSChange一样)。这使得CCD_ 22在CCD_。

或者,编译器可能只在调用堆栈上创建vector,并为自己保存一个对析构函数的调用,从而有效地使按值调用免费。

最新更新