前言
本文章对比了:小中大字符串在普通传值、传值移动、传左值引用、传右值引用、模板完美转发、内联版本等多种测试,对比各个方式的性能优异:
测试代码1
#include <iostream>
#include <string>
#include <chrono>
#include <utility>
#include <vector>
#include <functional>
// 测量函数
template<typename Func>
double measureTime(Func&& func, int iterations) {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
func();
}
auto end = std::chrono::high_resolution_clock::now();
return std::chrono::duration<double, std::milli>(end - start).count();
}
class Media {
public:
std::string mPath;
// 0. 传值
void setPath0(std::string path) {
mPath = path;
}
// 1. 传值移动
void setPath1(std::string path) {
mPath = std::move(path);
}
// 2. 传左值引用
void setPath2(const std::string& path) {
mPath = path;
}
// 3. 传右值引用
void setPath3(std::string&& path) {
mPath = std::move(path);
}
// 4. 模板完美转发
template<typename T>
void setPath4(T&& path) {
mPath = std::forward<T>(path);
}
// 5. 内联版本
inline void setPathInline(const std::string& path) {
mPath = path;
}
};
// 结果存储结构
struct TestResult {
std::string name;
double time;
};
int main() {
const int N = 10000;
const int WARMUP = 1000; // 预热迭代
// 测试不同大小的字符串
std::vector<std::pair<std::string, std::string>> testStrings = {
{"小字符串(24字节)", std::string(24, 'x')},
{"中等字符串(1KB)", std::string(1024, 'x')},
{"大字符串(1MB)", std::string(1024 * 1024, 'x')}
};
for (const auto& [sizeDesc, bigString] : testStrings) {
std::cout << "\n===== 测试 " << sizeDesc << " =====\n";
std::vector<TestResult> results;
Media media;
// 预热
for (int i = 0; i < WARMUP; ++i) {
media.mPath = bigString;
}
// 0. 值传递版本测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath0(testStr);
}, N);
results.push_back({"setPath0(值传递,左值传参)", time});
}
// 1. 传值版本测试 (左值)
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath1(testStr);
}, N);
results.push_back({"setPath1(传值移动,左值传参)", time});
}
// 传值 + 右值传参测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath1(std::move(testStr));
testStr = bigString; // 还原数据
}, N);
results.push_back({"setPath1(传右值,右值传参)", time});
}
// 2. 传左值引用测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath2(testStr);
}, N);
results.push_back({"setPath2(传左值引用)", time});
}
// 3. 传右值引用测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath3(std::move(testStr));
testStr = bigString; // 还原数据
}, N);
results.push_back({"setPath3(传右值引用)", time});
}
// 4. 模板完美转发测试(左值)
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath4(testStr);
}, N);
results.push_back({"setPath4(模板完美转发,左值传参)", time});
}
// 模板完美转发测试(右值)
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath4(std::move(testStr));
testStr = bigString; // 还原数据
}, N);
results.push_back({"setPath4(模板完美转发,右值传参)", time});
}
// 5. 内联版本测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPathInline(testStr);
}, N);
results.push_back({"setPathInline(内联版本)", time});
}
// 6. 直接赋值测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.mPath = testStr;
}, N);
results.push_back({"直接赋值", time});
}
// 7. 直接赋值 + 移动
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.mPath = std::move(testStr);
testStr = bigString; // 还原数据
}, N);
results.push_back({"直接赋值 + 移动", time});
}
// 输出结果并排序
std::sort(results.begin(), results.end(),
[](const TestResult& a, const TestResult& b) {
return a.time < b.time;
});
std::cout << "性能排名 (从快到慢):\n";
for (size_t i = 0; i < results.size(); ++i) {
std::cout << i+1 << ". " << results[i].name
<< " 耗时: " << results[i].time << " 毫秒";
if (i == 0) {
std::cout << " (基准)";
} else {
double slowdown = (results[i].time / results[0].time - 1.0) * 100.0;
std::cout << " (慢 " << slowdown << "%)";
}
std::cout << "\n";
}
}
return 0;
}
测试结果:
测试代码2:
#include <iostream>
#include <string>
#include <chrono>
#include <utility>
#include <vector>
#include <functional>
#include <iomanip>
#include <random>
#include <numeric>
// 测量函数
template<typename Func>
double measureTime(Func&& func, int iterations) {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < iterations; ++i) {
func();
}
auto end = std::chrono::high_resolution_clock::now();
return std::chrono::duration<double, std::milli>(end - start).count();
}
//#define RESTORE_DATA //还原数据
class Media {
public:
std::string mPath;
void setPath0(std::string path) {
mPath = path;
}
void setPath1(std::string path) {
mPath = std::move(path);
}
void setPath2(const std::string& path) {
mPath = path;
}
void setPath3(std::string&& path) {
mPath = std::move(path);
}
template<typename T>
void setPath4(T&& path) {
mPath = std::forward<T>(path);
}
inline void setPathInline(const std::string& path) {
mPath = path;
}
};
struct TestResult {
std::string name;
double time;
};
// 格式化输出浮点数
std::string formatDouble(double value, int precision = 2) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(precision) << value;
return oss.str();
}
int main() {
const int N = 10000;
const int WARMUP = 1000;
std::vector<std::pair<std::string, std::string>> testStrings = {
{"小字符串(24字节)", std::string(24, 'x')},
{"中等字符串(1KB)", std::string(1024, 'x')},
{"大字符串(1MB)", std::string(1024 * 1024, 'x')}
};
for (const auto& [sizeDesc, bigString] : testStrings) {
std::cout << "\n===== 测试 " << sizeDesc << " =====\n";
std::vector<TestResult> results;
Media media;
// 预热
for (int i = 0; i < WARMUP; ++i) {
media.mPath = bigString;
}
// 0. 值传递版本测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath0(testStr);
}, N);
results.push_back({"setPath0(值传递,左值传参)", time});
}
// 0. 值传递,移动语义测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath0(std::move(testStr));
#ifdef RESTORE_DATA
testStr = bigString;
#endif
}, N);
results.push_back({"setPath0(值传递,移动语义)", time});
}
// 1. 传值版本测试 (左值)
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath1(testStr);
}, N);
results.push_back({"setPath1(传值移动,左值传参)", time});
}
// 传值 + 右值传参测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath1(std::move(testStr));
#ifdef RESTORE_DATA
testStr = bigString;
#endif
}, N);
results.push_back({"setPath1(传右值,右值传参)", time});
}
// 2. 传左值引用测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath2(testStr);
}, N);
results.push_back({"setPath2(传左值引用)", time});
}
// 2. 传左值引用测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath2(std::move(testStr));
}, N);
results.push_back({"setPath2(传右值引用)", time});
}
// 3. 传右值引用测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath3(std::move(testStr));
#ifdef RESTORE_DATA
testStr = bigString;
#endif
}, N);
results.push_back({"setPath3(传右值引用)", time});
}
// 4. 模板完美转发测试(左值)
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath4(testStr);
}, N);
results.push_back({"setPath4(模板完美转发,左值传参)", time});
}
// 模板完美转发测试(右值)
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPath4(std::move(testStr));
#ifdef RESTORE_DATA
testStr = bigString;
#endif
}, N);
results.push_back({"setPath4(模板完美转发,右值传参)", time});
}
// 5. 内联版本测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.setPathInline(testStr);
}, N);
results.push_back({"setPathInline(内联版本)", time});
}
// 6. 直接赋值测试
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.mPath = testStr;
}, N);
results.push_back({"直接赋值", time});
}
// 7. 直接赋值 + 移动
{
std::string testStr = bigString;
double time = measureTime([&]() {
media.mPath = std::move(testStr);
#ifdef RESTORE_DATA
testStr = bigString;
#endif
}, N);
results.push_back({"直接赋值 + 移动", time});
}
// 输出结果
std::cout << "\n性能对比:\n";
std::cout << std::setw(45) << std::left << "方法"
<< std::setw(15) << "耗时(ms)"
<< "性能比较\n";
std::cout << std::string(80, '-') << "\n";
double baselineTime = results[0].time; // 使用第一个测试作为基准
for (const auto& result : results) {
double percentage = ((baselineTime) / result.time) * 100.0;
std::cout << std::setw(45) << std::left << result.name
<< std::setw(15) << formatDouble(result.time);
if (std::abs(percentage) < 0.1) {
std::cout << "基准性能";
} else if (percentage > 0) {
std::cout << "快 " << formatDouble(percentage) << "%";
} else {
std::cout << "慢 " << formatDouble(-percentage) << "%";
}
std::cout << "\n";
}
}
return 0;
}
测试结果:
需要还原数据:
不需要还原数据: 注释#define RESTORE_DATA
结论
1. 首先注意第一点,函数内适当使用移动语义确实可以提高效率,需要还原数据时大概在30%左右,不需要还原数据时,效率提高巨大。
2. 需要注意的是,如果传递给函数的值也是move过的话,反而因为move会把原来的变量给清除,所以如果后面还需要的话,需要还原,其实效率提高并没有多少。
3. 接上一条,但是如果数据不需要了,不需要还原数据,那么效率提高将会极大,
具体代码可以将代码开头的 #define RESTORE_DATA给注释掉。
4. 如果需要还原数据,并且兼顾代码更好写,那么左值引用是个不错的选择,还不用写还原数据:
可以说,即高效又方便。比起内联函数和完美转发,效率不遑多让。
5. 如果不需要还原数据,那么下图框出来的几个都可以,setPath3右值引用兼顾代码好写和性能
6. 由数据可知,完美转发虽然写法复杂,但是兼容性好,性能高,如果掌握了其实更好用
完美转发的优劣势:
- 完美转发能够保留参数的左值/右值属性,使函数能够根据参数的原始类型进行正确的转发,避免了不必要的拷贝和转换,提高了性能
- 无需为左值和右值分别编写重载函数,一个模板函数就能处理多种参数类型,减少代码冗余
- 可以编写通用的工厂函数,将参数完美转发给构造函数,便于实现代理、装饰器等设计模式
- 完美转发会保留参数的const属性,确保类型安全
- 库设计者可以提供更灵活的接口,而不必担心参数传递的效率问题
最适合使用完美转发的场景
通用工厂函数:
- 转发包装器/代理函数:
- 当你需要包装一个函数,同时保留其参数的所有特性时
可变参数模板函数:
- 处理不定数量、不定类型的参数时
构建通用容器:
- 实现如
emplace_back
等需要直接构造对象的容器方法
- 实现如
中间层API设计:
- 当你的函数只是将参数传递给另一个函数,而不做任何处理时
性能关键的代码:
- 需要避免不必要拷贝的性能敏感代码
不适合使用完美转发的场景
简单的函数接口:
- 如果参数类型固定且简单,使用常规引用可能更清晰
需要明确参数类型的API:
- 当你希望API使用者明确知道参数如何传递时
教学或入门级代码:
- 对于学习者来说,完美转发可能过于复杂
需要对参数进行多次使用的场景:
- 由于右值引用可能被移动,如果需要多次使用参数,完美转发可能不是最佳选择
完美转发其他实例代码
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <type_traits>
// 用于显示参数类型的辅助函数
template<typename T>
void showValueCategory(const std::string& funcName, T&& param) {
std::cout << funcName << ": ";
// 使用完整的类型判断
if (std::is_same<T, std::remove_reference_t<T>>::value) {
std::cout << "参数是右值" << std::endl;
}
else if (std::is_lvalue_reference<T>::value) {
std::cout << "参数是左值" << std::endl;
}
else {
std::cout << "参数是转发的右值" << std::endl;
}
}
//===== 示例1:字符串包装类 =====
class Message {
public:
// 构造函数使用完美转发
template<typename T>
Message(T&& msg) : content_(std::forward<T>(msg)) {
showValueCategory("Message构造", std::forward<T>(msg));
// 显示更详细的类型信息
std::cout << " 类型信息: "
<< (std::is_lvalue_reference<T>::value ? "左值引用" :
std::is_rvalue_reference<T>::value ? "右值引用" : "值类型")
<< std::endl;
}
const std::string& getContent() const { return content_; }
private:
std::string content_;
};
//===== 示例2:工厂函数 =====
template<typename T, typename... Args>
std::unique_ptr<T> createObject(Args&&... args) {
std::cout << "创建新对象...\n";
// 显示每个参数的类型信息
(showValueCategory("工厂函数参数", std::forward<Args>(args)), ...);
return std::make_unique<T>(std::forward<Args>(args)...);
}
//===== 示例3:通用打印函数 =====
class Printer {
public:
template<typename T>
void print(T&& value) {
showValueCategory("打印函数", std::forward<T>(value));
std::cout << "打印内容: " << value << std::endl;
}
};
//===== 示例4:参数转发容器 =====
template<typename T>
class Container {
public:
template<typename Arg>
void add(Arg&& arg) {
showValueCategory("Container添加", std::forward<Arg>(arg));
data_.emplace_back(std::forward<Arg>(arg));
}
void showAll() const {
std::cout << "容器内容:";
for (const auto& item : data_) {
std::cout << item.getContent() << " ";
}
std::cout << std::endl;
}
private:
std::vector<T> data_;
};
// 辅助函数:显示对象移动状态
void showMove(const std::string& str) {
std::cout << "String '" << str << "' 被移动" << std::endl;
}
int main() {
std::cout << "\n===== 完美转发示例 =====\n";
// 测试1:基础构造函数转发
std::cout << "\n1. 测试基础构造函数转发:" << std::endl;
{
std::string str = "Hello"; // 创建左值
std::cout << "传递左值:" << std::endl;
Message msg1(str); // 传递左值
std::cout << "传递右值字面量:" << std::endl;
Message msg2("World"); // 传递右值字面量
std::cout << "传递移动的值:" << std::endl;
Message msg3(std::move(str)); // 传递右值(移动)
}
// 测试2:工厂函数转发
std::cout << "\n2. 测试工厂函数转发:" << std::endl;
{
std::string name = "Factory Object";
std::cout << "\n使用左值创建:" << std::endl;
auto msg1 = createObject<Message>(name);
std::cout << "\n使用右值创建:" << std::endl;
auto msg2 = createObject<Message>(std::string("Direct String"));
std::cout << "\n使用字符串字面量创建:" << std::endl;
auto msg3 = createObject<Message>("Literal String");
}
// 测试3:打印函数转发
std::cout << "\n3. 测试打印函数转发:" << std::endl;
{
Printer printer;
std::string text = "Left Value Text";
std::cout << "打印左值:" << std::endl;
printer.print(text);
std::cout << "打印右值:" << std::endl;
printer.print(std::string("Right Value Text"));
std::cout << "打印字面量:" << std::endl;
printer.print("Literal Text");
}
// 测试4:容器转发
std::cout << "\n4. 测试容器转发:" << std::endl;
{
Container<Message> container;
std::string item1 = "First";
std::cout << "添加左值:" << std::endl;
container.add(item1);
std::cout << "添加右值:" << std::endl;
container.add(std::string("Second"));
std::cout << "添加字面量:" << std::endl;
container.add("Third");
container.showAll();
}
return 0;
}