「函數該用返回值還是輸出參數?」是困擾許多開發者的設計決策。本文從代碼審查實戰出發,對比「返回值直接攜帶結果」與「輸出參數承載結果、返回值表達狀態」兩種風格,總結判斷依據與最佳實踐。
// 風格 A:輸出參數(原作者習慣)
void GetExtent(Extent &out) { out = m_extent; }
// 風格 B:直接返回(審查者建議)
Extent& GetExtent() { return m_extent; }
優勢 B:
適用場景**:獲取器、純計算、無失敗可能、結果為值語義類型(小對象、支援移動語義)。
// 原設計:返回對象,失敗拋異常/返回哨兵
Property SearchProperty(string name) {
for (auto &p : props) if (p.name == name) return p;
throw "not found"; // 或返回 static Property invalid;
}
// 改進:bool + 輸出參數
bool SearchProperty(const string &name, Property &out) {
for (auto &p : props) if (p.name == name) { out = p; return true; }
return false;
}
優勢**:
需要表達「操作是否成功/值是否存在」?
├─ 否 → 直接返回結果值(支援鏈式、移動語義)
└─ 是 → 返回 bool/錯誤碼/optional,結果通過輸出參數/返回值承載
├─ C++17+ → 推薦 std::optional 統一語義
├─ C++11-14 → bool + T& out / 錯誤碼 enum + T& out
└─ C / 老代碼 → int 錯誤碼 + T* out
#include <optional>
#include <string>
#include <vector>
struct Property { std::string name; int value; };
class Registry {
std::vector<Property> props;
public:
// 查找:可能不存在
std::optional<Property> find(const std::string &name) const {
for (auto &p : props) if (p.name == name) return p;
return std::nullopt;
}
// 純獲取:必定存在(前置條件)
const Property& get(const std::string &name) const {
for (auto &p : props) if (p.name == name) return p;
throw std::out_of_range("Property not found"); // 契約違反
}
};
// 調用
if (auto opt = reg.find("foo"); opt) {
use(*opt); // 解包
} else {
handle_missing();
}
| 場景 | 推薦形式 | 關鍵理由 |
|---|---|---|
| 純查詢、必定成功 | 返回值 (引用/值) | 鏈式調用、CQS 原則 |
| 查詢、可能失敗 | `std::optional | 強制處理失敗、無異常開銷 |
| 修改狀態、需報錯 | 錯誤碼 enum / `std::expected` (C++23) | 豐富錯誤語義、可擴展 |
| 多返回值 | `std::tuple` / `struct` / `std::pair` | 類型安全、結構化綁定 |
| C 語言介面 | `int err_code, T* out` | ABI 穩定、跨語言調用 |
「返回值表達『主要結果』,輸出參數表達『附帶產出』;若主要結果可能不存在,將『存在性』提升為返回值主體,主要結果轉為附帶產出。」