C++ 引用折疊(Reference Collapsing)的具體使用
一句話定義
當(dāng)“引用的引用”在模板、typedef / using、auto、decltype 中出現(xiàn)時(shí),
編譯器按照固定規(guī)則把它折疊為單一引用類型。
一、為什么需要引用折疊?
C++ 語(yǔ)法層面 不允許顯式寫:
int&&& x; // 非法
但在模板推導(dǎo)后,這種“引用的引用”會(huì)隱式產(chǎn)生:
template<typename T> void f(T&& x);
當(dāng)這樣調(diào)用:
int a; f(a); // T = int&
于是參數(shù)類型變成:
T&& → int& && ← 出現(xiàn)“引用的引用”
引用折疊規(guī)則的存在目的:
讓模板推導(dǎo)在語(yǔ)法上始終合法
二、唯一的折疊規(guī)則
C++ 標(biāo)準(zhǔn)中的核心規(guī)則
只要出現(xiàn)左值引用 &,最終結(jié)果就是 &
只有 && && 才會(huì)折疊成 &&
四種情況
| 原始形式 | 折疊結(jié)果 |
|---|---|
| T& & | T& |
| T& && | T& |
| T&& & | T& |
| T&& && | T&& |
口訣版
“& 是霸道的,只要出現(xiàn)就贏”
三、引用折疊只會(huì)在這些地方發(fā)生
不是任何地方都會(huì)發(fā)生引用折疊
會(huì)發(fā)生的場(chǎng)景
- 模板類型推導(dǎo)
- using / typedef
- auto
- decltype
- std::forward / 完美轉(zhuǎn)發(fā)
不會(huì)發(fā)生的場(chǎng)景
int&& && x; // 語(yǔ)法錯(cuò)誤(非模板上下文)
四、模板推導(dǎo)中的引用折疊(最重要)
1 萬(wàn)能引用(Forwarding Reference)
template<typename T> void f(T&& x);
調(diào)用情況分析
int a = 10; f(a);
推導(dǎo)過(guò)程:
T = int& T&& = int& && → 折疊 → int&
x 是 左值引用
f(10);
T = int T&& = int&&
x 是 右值引用
結(jié)論(非常重要)
T&& 在模板中 ≠ 右值引用
它是 萬(wàn)能引用(forwarding reference)
五、auto中的引用折疊
示例 1:auto&&
int a = 10; auto&& x = a;
推導(dǎo):
auto = int& auto&& = int& && → int&
auto&& y = 10;
auto = int auto&& = int&&
結(jié)論
auto&& // 永遠(yuǎn)是萬(wàn)能引用
六、using / typedef中的引用折疊
示例
using LRef = int&; using RRef = int&&; LRef& → int& LRef&& → int& RRef& → int& RRef&& → int&&
using 不會(huì)阻止引用折疊
七、decltype+ 引用折疊(最容易踩坑)
規(guī)則回顧
int x = 10; decltype(x) // int decltype((x)) // int& ← 注意括號(hào)!
示例
decltype((x))&& y = x;
推導(dǎo):
decltype((x)) = int& int& && → int&
結(jié)論
decltype 的結(jié)果本身可能帶引用
再加 && 就會(huì)觸發(fā)引用折疊
八、完美轉(zhuǎn)發(fā) = 引用折疊 + 值類別保持
std::forward的本質(zhì)
template<typename T> T&& forward(remove_reference_t<T>& param);
使用示例
template<typename T>
void wrapper(T&& arg) {
foo(std::forward<T>(arg));
}
左值情況
T = int& forward<int&>(arg) → int&
右值情況
T = int forward<int>(arg) → int&&
引用折疊是完美轉(zhuǎn)發(fā)成立的核心機(jī)制
九、常見(jiàn)錯(cuò)誤 & 工程級(jí)坑點(diǎn)
誤以為T&&一定是右值引用
template<typename T> void f(T&& x); // 錯(cuò)
正解:
當(dāng)且僅當(dāng) T 是被推導(dǎo)出來(lái)的,T&& 才是萬(wàn)能引用
在非模板中使用&&期望折疊
void f(int&&&& x); //
忘了decltype((x))是引用
十、編譯器視角總結(jié)
引用折疊不是運(yùn)行期行為
它發(fā)生在模板實(shí)例化 + 類型替換階段
編譯流程中位置
模板推導(dǎo) → 生成候選類型 → 引用折疊 → 最終參數(shù)類型 → 代碼生成
十一、終極總結(jié)
1?? 引用折疊只在模板/類型推導(dǎo)中發(fā)生
2?? 規(guī)則只有一條:有 & 就是 &
3?? T&& + 模板推導(dǎo) = 萬(wàn)能引用
4?? 完美轉(zhuǎn)發(fā)的底層機(jī)制 = 引用折疊
5?? decltype((x)) 是最常見(jiàn)陷阱
Eigen/SLAM 中:引用折疊如何避免拷貝
一、Eigen 場(chǎng)景中的真實(shí)問(wèn)題
在 SLAM 代碼中經(jīng)常會(huì)寫:
Eigen::Matrix<double, 15, 15> H; Eigen::Matrix<double, 15, 1> b;
或者:
Eigen::Vector3d r; Eigen::Matrix3d J;
天真的接口(會(huì)拷貝)
void AddResidual(Eigen::VectorXd r) {
// 每次調(diào)用都會(huì)拷貝
}
Eigen 動(dòng)態(tài)矩陣 → 昂貴拷貝
二、Eigen 官方的解決方案:模板 + 表達(dá)式
Eigen 的核心思想
Matrix = 表達(dá)式
表達(dá)式不是值,而是 Expression Template
三、錯(cuò)誤寫法 vs 正確寫法(關(guān)鍵對(duì)比)
錯(cuò)誤:值傳遞
template<typename Derived>
void AddResidual(Eigen::MatrixBase<Derived> r) {
// 拷貝已經(jīng)發(fā)生
}
錯(cuò)誤:const 引用但阻斷右值
template<typename Derived> void AddResidual(const Eigen::MatrixBase<Derived>& r);
- 不能高效接收臨時(shí)表達(dá)式
- 失去 move / lazy evaluation 的機(jī)會(huì)
四、Eigen 推薦的“零拷貝”接口寫法
核心模式(引用折疊的舞臺(tái))
template<typename Derived>
void AddResidual(Eigen::MatrixBase<Derived>&& r) {
// 完美接收左值 or 右值
}
這是 Eigen 內(nèi)部大量使用的模式
五、引用折疊在這里如何工作?
情況 1:傳左值(已有殘差向量)
Eigen::Vector3d r; AddResidual(r);
推導(dǎo)過(guò)程
T = Eigen::Vector3d& T&& = Eigen::Vector3d& && → 折疊 → Eigen::Vector3d&
r 以 左值引用 進(jìn)入
無(wú)拷貝
情況 2:傳右值表達(dá)式(Eigen 的精華)
AddResidual(J * dx + r0);
這里:
J * dx + r0 → Eigen::CwiseBinaryOp<...>(表達(dá)式)
推導(dǎo)過(guò)程
T = CwiseBinaryOp<...> T&& = CwiseBinaryOp<...>&&
表達(dá)式對(duì)象 零拷貝傳入
計(jì)算 延遲到函數(shù)內(nèi)部
六、為什么const&在 Eigen 中不夠好?
問(wèn)題
const Eigen::MatrixBase<Derived>& r
- 強(qiáng)制綁定 const
- 阻止某些 move / eval
- 某些表達(dá)式不能安全延遲
Eigen 官方風(fēng)格
template<typename Derived> void foo(Eigen::MatrixBase<Derived>&& x);
并在內(nèi)部:
auto&& expr = std::forward<Derived>(x);
七、SLAM 后端:殘差 & Jacobian 的真實(shí)例子
一個(gè)真實(shí)的誤差項(xiàng)接口
template<typename ResidualDerived, typename JacobianDerived>
void AddFactor(ResidualDerived&& r,
JacobianDerived&& J) {
using RType = std::remove_reference_t<ResidualDerived>;
using JType = std::remove_reference_t<JacobianDerived>;
// 僅在必要時(shí) eval
const RType& r_eval = r;
const JType& J_eval = J;
// 參與正規(guī)方程
}
調(diào)用方式(零拷貝)
AddFactor(
J * dx + r0, // 右值表達(dá)式
J.transpose() * J // 右值表達(dá)式
);
八、結(jié)合李代數(shù)(SO(3) / SE(3))
常見(jiàn) SLAM 代碼
template<typename TangentDerived>
void ApplyUpdate(TangentDerived&& delta) {
// delta 是 Eigen::Matrix<double, 6, 1> 或表達(dá)式
xi_ = Sophus::SE3d::exp(delta) * xi_;
}
左值情況
Eigen::Matrix<double, 6, 1> dx; ApplyUpdate(dx);
→ delta 折疊為 Eigen::Matrix<...>&
右值情況
ApplyUpdate(H.ldlt().solve(b));
→ delta 是 Eigen 表達(dá)式
→ 無(wú)臨時(shí)矩陣拷貝
九、std::forward 在 Eigen / SLAM 中的作用
正確姿勢(shì)
template<typename Derived>
void Foo(Derived&& x) {
Bar(std::forward<Derived>(x));
}
錯(cuò)誤姿勢(shì)
Bar(x); // 丟失值類別 → 右值變左值
十、性能視角總結(jié)(非常重要)
| 寫法 | 是否拷貝 | 表達(dá)式延遲 | 工程推薦 |
|---|---|---|---|
| 值傳遞 | ? | ? | 禁用 |
| const& | ? | 部分 | 一般 |
| T&& + forward | ?? | ?? | ??? |
十一、Eigen / SLAM 模板黃金法則
任何可能接收 Eigen 表達(dá)式 / 李代數(shù)增量的接口:
template<typename T> void func(T&& x);
并在內(nèi)部:
auto&& v = std::forward<T>(x);
十二、在 SLAM 系統(tǒng)中應(yīng)該立刻用的模式
后端殘差
template<typename R, typename J>
void AddResidual(R&& r, J&& J) { ... }
狀態(tài)更新
template<typename DX> void Update(DX&& dx);
圖優(yōu)化 factor 構(gòu)造
template<typename Measurement> Factor(Measurement&& z);
總結(jié)
Eigen 的高性能 = 表達(dá)式模板
表達(dá)式模板的生命線 = 引用折疊 + 完美轉(zhuǎn)發(fā)
到此這篇關(guān)于C++ 引用折疊(Reference Collapsing)的具體使用的文章就介紹到這了,更多相關(guān)C++ 引用折疊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于C語(yǔ)言實(shí)現(xiàn)創(chuàng)意多彩貪吃蛇游戲
這篇文章主要介紹了如何利用C語(yǔ)言實(shí)現(xiàn)一個(gè)創(chuàng)意多彩貪吃蛇游戲,這是一個(gè)純C語(yǔ)言外加easyx庫(kù)的繪圖函數(shù)制作而成的有趣小游戲,無(wú)需引入額外資源,感興趣的可以動(dòng)手嘗試一下2022-08-08
opencv3/C++ 將圖片轉(zhuǎn)換為視頻的實(shí)例
今天小編就為大家分享一篇opencv3/C++ 將圖片轉(zhuǎn)換為視頻的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-12-12
VS?Code+msys2配置Windows系統(tǒng)下C/C++開(kāi)發(fā)環(huán)境
我們?cè)趙indows10中使用VS Code做C++程序開(kāi)發(fā)過(guò)程中,需要安裝MSYS2和MinGW,下面這篇文章主要給大家介紹了關(guān)于VS?Code+msys2配置Windows系統(tǒng)下C/C++開(kāi)發(fā)環(huán)境的相關(guān)資料,需要的朋友可以參考下2022-12-12
淺析內(nèi)存對(duì)齊與ANSI C中struct型數(shù)據(jù)的內(nèi)存布局
當(dāng)在C中定義了一個(gè)結(jié)構(gòu)類型時(shí),它的大小是否等于各字段(field)大小之和?編譯器將如何在內(nèi)存中放置這些字段?ANSI C對(duì)結(jié)構(gòu)體的內(nèi)存布局有什么要求?而我們的程序又能否依賴這種布局2013-09-09
VC創(chuàng)建DLL動(dòng)態(tài)鏈接庫(kù)的方法
這篇文章主要介紹了VC創(chuàng)建DLL動(dòng)態(tài)鏈接庫(kù)的方法,實(shí)例分析VC創(chuàng)建動(dòng)態(tài)鏈接庫(kù)的完整步驟,需要的朋友可以參考下2015-05-05
C語(yǔ)言實(shí)現(xiàn)冒泡排序算法的示例詳解
這篇文章主要介紹了C語(yǔ)言如何實(shí)現(xiàn)冒泡排序算法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
關(guān)于C++動(dòng)態(tài)分配內(nèi)存的介紹
今天小編就為大家分享一篇關(guān)于關(guān)于C++動(dòng)態(tài)分配內(nèi)存的介紹,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12
C語(yǔ)言實(shí)現(xiàn)飛機(jī)大戰(zhàn)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)飛機(jī)大戰(zhàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06

