前言
? ? ? ? 本文章對比了:小中大字符串在普通傳值、傳值移動、傳左值引用、傳右值引用、模板完美轉發、內聯版本等多種測試,對比各個方式的性能優異:
測試代碼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_DATAtestStr = 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_DATAtestStr = 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_DATAtestStr = 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_DATAtestStr = 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_DATAtestStr = 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;
}