关于是否按值或按引用传递参数有很多问题和答案。这个答案https://stackoverflow.com/a/51706522/2492801似乎表明通过值传递和移动对于左值来说并不是最佳。 C++17 的这个基准似乎证明了这一点:https://quick-bench.com/q/8pCCiw5tBtGwh8XaR9EsBTLZTzw。 “BM_PassLvalueByValueAndMove”的情况是最慢的。通过引用传递 lvalue 的情况(“BM_PassLvalueByReferenceAndCopy”和“BM_PassLValueToStructOfferingBoth”)速度更快。
但是 clang-tidy 检查“modernize-pass-by-value”无条件建议在构造函数中按值传递:https://clang.llvm.org/extra/clang-tidy/checks/modernize/pass-按值.html。这不是一个糟糕的建议吗?它可能对右值有益,但对左值有缺点。
但实际上这似乎也取决于语言标准。当我在基准测试中将“C++17”更改为“C++20”时(请参阅https://quick-bench.com/q/U9di84h6HaNQw7urp6qEiTjYPMI),突然情况“BM_PassLvalueByValueAndMove”并不比其他情况慢lvalue 案例不再存在。对于“C++23”来说也是如此。
这是否意味着 clang-tidy 的提议是正确的,但仅从 C++20 开始?标准的哪些变化使得价值传递和左值变得更好?
基准代码:
#include <benchmark/benchmark.h>
#include <string>
#include <utility>
struct PassByValueAndMove
{
explicit PassByValueAndMove(std::string string)
: m_string(std::move(string))
{
}
std::string m_string;
};
struct PassByReferenceAndCopy
{
explicit PassByReferenceAndCopy(const std::string& string)
: m_string(string)
{
}
std::string m_string;
};
struct PassBoth
{
explicit PassBoth(std::string&& string)
: m_string(std::move(string))
{
}
explicit PassBoth(const std::string& string)
: m_string(string)
{
}
std::string m_string;
};
static void BM_PassRvalueByReferenceAndCopy(benchmark::State& state)
{
for (auto _ : state)
{
static_cast<void>(_);
{
PassByReferenceAndCopy instance("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
benchmark::DoNotOptimize(instance);
}
}
}
BENCHMARK(BM_PassRvalueByReferenceAndCopy);
static void BM_PassRvalueByValueAndMove(benchmark::State& state)
{
for (auto _ : state)
{
static_cast<void>(_);
{
PassByValueAndMove instance("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
benchmark::DoNotOptimize(instance);
}
}
}
BENCHMARK(BM_PassRvalueByValueAndMove);
static void BM_PassLvalueByReferenceAndCopy(benchmark::State& state)
{
for (auto _ : state)
{
static_cast<void>(_);
{
const std::string dummy_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
PassByReferenceAndCopy instance(dummy_string);
benchmark::DoNotOptimize(instance);
}
}
}
BENCHMARK(BM_PassLvalueByReferenceAndCopy);
static void BM_PassLvalueByValueAndMove(benchmark::State& state)
{
for (auto _ : state)
{
static_cast<void>(_);
{
const std::string dummy_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
PassByValueAndMove instance(dummy_string);
benchmark::DoNotOptimize(instance);
}
}
}
BENCHMARK(BM_PassLvalueByValueAndMove);
static void BM_PassRvalueToStructOfferingBoth(benchmark::State& state)
{
for (auto _ : state)
{
static_cast<void>(_);
{
PassBoth instance("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
benchmark::DoNotOptimize(instance);
}
}
}
BENCHMARK(BM_PassRvalueToStructOfferingBoth);
static void BM_PassLValueToStructOfferingBoth(benchmark::State& state)
{
for (auto _ : state)
{
static_cast<void>(_);
{
const std::string dummy_string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
PassBoth instance(dummy_string);
benchmark::DoNotOptimize(instance);
}
}
}
BENCHMARK(BM_PassLValueToStructOfferingBoth);
clang-tidy 检查仅在您有一个采用 const 引用的构造函数时才会触发,而如果您还有一个采用右值引用的构造函数则不会触发(请参阅:https://godbolt.org/z/4zWxoWoT4)
我认为您可以同意建议的修复程序比之前的修复程序在性能和可读性方面都取得了胜利:
struct S {
std::string member;
explicit S(const std::string& s) : member(s) {}
// clang-tidy suggests
// explicit S(std::string s) : member(std::move(s)) {}
};
按值获取并移动只会导致左值额外移动 1 次(纯右值不会额外移动)。
担心这一点可能是一个不成熟的优化,因为单个动作应该非常快。这甚至不是一个好的过早优化,因为如果这确实是性能瓶颈,那么您应该转而采用 lambda 并就地构建值以完全摆脱移动和复制。