關于有害的過度使用 std::move

翻譯:2023 11 月 24 日On harmful overuse of std::move

cppreference std::move

論 std::move 的有害過度使用 - The Old New Thing

C++ 的 std::move 函數將其參數轉換為右值引用,這使得其內容可以被另一個操作“消費”(移動)。但是,在你為這個新表達能力興奮不已時,請注意不要過度使用它。

std::string get_name(int id) {std::string name = std::to_string(id);/* 假設這里進行了其他計算 */return std::move(name); // 過度使用 move 的錯誤示范
}

你可能認為你在通過說“嘿,你看,我在之后不會使用我的局部變量 name 了,所以你可以直接把字符串移動到返回值里”來給編譯器一些幫助。不幸的是,你的“幫助”實際上造成了傷害。

添加 std::move 會導致返回語句不能滿足復制省略(Copy Elision) (通常稱為命名返回值優化,NRVO)的條件:返回的東西必須是與函數返回值類型相同的局部變量的名稱。添加的 std::move 阻止了 NRVO,返回值是通過移動構造函數從 name 變量構造的。

std::string get_name(int id) {std::string name = std::to_string(id);/* 假設這里進行了其他計算 */return name; // 正確方式:允許 NRVO
}

這次,我們直接返回 name,編譯器現在可以省略拷貝,直接將 name 變量放入返回值槽中,無需拷貝。(編譯器被允許但不強制進行此優化;但在實踐中,如果所有代碼路徑都返回同一個局部變量,所有編譯器都會這樣做。)

過度熱衷使用 std::move 的另一半問題發生在接收端。

extern void report_name(std::string name);void sample1() {std::string name = std::move(get_name()); // 過度使用 move 的錯誤示范
}void sample2() {report_name(std::move(get_name())); // 過度使用 move 的錯誤示范
}

在這兩個示例函數中,我們獲取 get_name() 的返回值,并顯式地 std::move 它到一個新的局部變量或函數參數中。這是另一個試圖幫忙卻最終幫倒忙的例子。

從一個匹配類型的值構造一個值(無論是局部變量還是函數參數)會被省略:匹配的值被直接存儲到局部變量或參數中,無需拷貝。但添加 std::move 阻止了此優化發生,該值將通過移動構造。

extern void report_name(std::string name);void sample1() {std::string name = get_name(); // 正確方式:允許初始化省略
}void sample2() {report_name(get_name()); // 正確方式:允許參數初始化省略
}

特別“精彩”的是當你把兩個錯誤結合在一起時。那樣的話,你把一個本來完全沒有拷貝或移動操作的序列,變成了一個創建了兩個額外的臨時對象、兩次額外的移動操作和兩次額外的析構操作的序列。

#include <memory>
struct S {S();S(S const&);S(S &&);~S();
};
extern void consume(S s);// 錯誤版本
S __declspec(noinline) f1() {S s;return std::move(s); // 錯誤 1:阻止 NRVO
}void g1() {consume(std::move(f1())); // 錯誤 2:阻止初始化省略
}

(展示MSVC 為錯誤版本 f1/g1 和正確版本 f2/g2 生成的匯編代碼,清晰地證明了錯誤版本進行了額外的移動構造和臨時對象操作,而正確版本利用 NRVO 和初始化省略實現了零拷貝/移動。)

以下是 msvc 的編譯器輸出:

; on entry, rcx says where to put the return value在入口處,rcx指出將返回值放在何處。
f1:mov     qword ptr [rsp+8], rcxpush    rbxsub     rsp, 48mov     rbx, rcx; construct local variable s on stack在堆棧上構造局部變量slea     rcx, qword ptr [rsp+64]call    S::S(); copy local variable to return value復制局部變量到返回值lea     rdx, qword ptr [rsp+64]mov     rcx, rbxcall    S::S(S &&); destruct the local variable s析構局部變量 slea     rcx, qword ptr [rsp+64]call    S::~S(); return the result返回結果mov     rax, rbxadd     rsp, 48pop     rbxretg1:sub     rsp, 40; call f1 and store into temporary variable調用f1并存儲到臨時變量中lea     rcx, qword ptr [rsp+56]call    f1(); copy temporary to outbound parameter復制臨時到出站參數mov     rdx, raxlea     rcx, qword ptr [rsp+48]call    S::S(S &&); call consume with the outbound parameter使用出站參數調用消費mov     rcx, raxcall    consume(S); clean up the temporary清理臨時的lea     rcx, qword ptr [rsp+56]call    S::~S(); returnadd     rsp, 40ret

請注意,調用 g1 會導致總共創建兩個額外的 S 副本,一個在 f1 中,另一個用于保存 f1 的返回值。

相比之下,如果我們使用 copy elision:

// Good version
S __declspec(noinline) f2()
{S s;return s;
}void g2()
{consume(f2());
}

那么 msvc 代碼生成是

; on entry, rcx says where to put the return value在入口處,rcx指出將返回值放在何處。
f2:push    rbxsub     rsp, 48mov     rbx, rcx; construct directly into return value (still in rcx)直接構造為返回值(仍在rcx中)call    S::S(); and return it并將其返回mov     rax, rbxadd     rsp, 48pop     rbxretg2:sub     rsp, 40; put return value of f1 directly into outbound parameter將f1的返回值直接放入出站參數中lea     rcx, qword ptr [rsp+48]call    f2(); call consume with the outbound parameter使用出站參數調用消費mov     rcx, eaxcall    consume(S); returnadd     rsp, 40ret

其他編譯器(GCC, Clang, ICC ICX)也有類似結果。在 GCC, Clang 和 ICX 中,你可以啟用 -Wpessimizing-move 警告來提示你何時犯了這些錯誤。


(文章評論區精選翻譯)

  • 紅樓鍮鍮:std::move 濫用與 auto&& 濫用結合時會更糟:auto&& name = get_name(); 會不必要地創建一個抑制 NRVO 的引用;auto&& name = std::move(get_name()); 實際上會創建一個懸垂引用,因為 C++ 不會延長臨時對象的生命周期如果在其和聲明的局部引用之間存在函數調用。有趣的是,auto&& name = static_cast<std::string &&>(get_name()); 會產生一個有效的引用!我懷疑用 static_cast 替換 std::move 可能會恢復 NRVO。
  • Neil Rashbrook: (回應上條)假設我做對了,如果你移動(move)或轉換(cast)了值,你就得不到優化。
  • Kevin Norris: (回應上條)有趣的是,似乎 MSVC 能用 return static_cast<T&&>(...) 優化掉移動,但 GCC 和 Clang 不能。雖然 GCC 和 Clang 會對 return std::move(...); 發出警告,但它們不會對 return static_cast<T&&>(...) 發出警告。
  • Solomon Ucko: 我從這里得到的是:std::move 是一個轉換操作(cast)。它應該以與 static_cast 完全相同的謹慎態度對待。只在你能清楚地說明這個轉換在形式上做了什么(特別是:為什么你期望編譯器在該值被轉換為右值引用時以不同方式處理它?)、為什么該用例不滿足復制省略(包括但不限于 NVRO)的條件、以及被移動源對象(moved-from)之后會怎樣(特別是:你應該合理確信被移動源對象永遠不會再被使用)時使用它。
  • Simon Farnsworth: 注意,與 C++17 及以后的強制 NRVO 不同,Rust 的 NRVO完全是可選的。它在某些情況下甚至是不健全的,因此在某些版本中被禁用。
  • Kevin Norris: 有沒有什么副作用或其他我看不到的原因,導致 return std::move(name); 的情況不能被優化掉?或者這只是標準遺漏了一個機會,而編譯器被標準所約束?
  • (其他回復 Kevin): 標準要求如果返回值是純右值(prvalue)則必須省略拷貝,但 std::move(foo) 是一個將亡值(xvalue)。標準允許在 Raymond 描述的 NVRO 情況以及其他一些涉及異常和協程的特殊場景中進行省略。在所有其他情況下,省略只允許在 “as-if” 規則下,這要求編譯器證明在可觀察行為上沒有差異——這通常很困難。廣義上將亡值情況下的省略可能無效,因為對象可能在函數調用前就存在,且對其他部分可見或并發訪問,跳過其析構會造成問題。標準可以為“誤用了 std::move() 的 NVRO 情況”開特例,但告訴人們不要那樣做更簡單。

核心總結:

Raymond Chen 的文章核心警告了在 C++ 中 過度和不必要地使用 std::move 反而會損害性能,特別是在涉及函數返回值和初始化新對象時,因為它會阻止編譯器進行關鍵的優化:

  1. 在函數返回值上過度使用 std::move
    • 錯誤做法: return std::move(local_variable);
    • 危害: 阻止了 命名返回值優化 (NRVO)。NRVO 允許編譯器直接在函數的返回值槽中構造局部變量,從而完全省略拷貝/移動構造
    • 正確做法: 直接返回局部變量:return local_variable;。這滿足 NRVO 的條件,編譯器(在實踐中通常會)進行優化,實現零拷貝/移動。
  2. 在接收函數返回值初始化新對象時過度使用 std::move:
    • 錯誤做法 (初始化變量): T var = std::move(func());
    • 錯誤做法 (傳遞參數): some_func(std::move(func()));
    • 危害: 阻止了 初始化省略 (Initialization Elision)。當使用相同類型的值初始化另一個對象(變量或參數)時,編譯器可以直接將源值用作目標對象,省略中間的臨時對象和拷貝/移動操作
    • 正確做法: 直接使用返回值初始化:T var = func();some_func(func());。這允許編譯器進行初始化省略。
  3. 雙重錯誤(最糟糕): 如果在返回函數中錯誤使用 std::move 阻止了 NRVO,并且在調用函數中錯誤使用 std::move 阻止了初始化省略,那么本來可以完全零拷貝/移動的操作鏈(func() 內部構造 -> 直接用作返回值 -> 直接用作參數或變量),會變成:
    • 在返回函數中:一次移動構造(因為 NRVO 被阻止)。
    • 在調用函數中:創建一個臨時對象(存儲 func() 的返回值),然后通過移動構造初始化目標變量或參數(因為初始化省略被阻止)。
    • 結果:創建了兩個額外的臨時對象,發生了兩次額外的移動操作,并進行了兩次額外的析構操作,性能顯著下降。

核心教訓與建議:

  • 優先信任編譯器優化: 在簡單返回局部變量或直接用返回值初始化相同類型對象時,不要添加 std::move。讓編譯器利用 NRVO 和初始化省略規則進行零拷貝優化。
  • std::move 視為強制類型轉換: 像對待 static_cast 一樣謹慎使用 std::move。僅在需要顯式啟用移動語義(例如,要將對象的所有權轉移給函數,或你知道源對象不再需要且移動比拷貝更廉價)時使用。
  • 理解使用 std::move 的后果: 使用 std::move 后,被移動的對象處于有效但未指定狀態,不應再依賴其內容
  • 啟用編譯器警告: 使用 GCC, Clang 或 ICC 時,開啟 -Wpessimizing-move(或等效警告)來檢測這種潛在的性能反優化。
  • 避免 auto&&std::move 的致命組合: auto&& name = std::move(func()); 容易創建懸垂引用,因為 func() 返回的臨時對象生命周期不會因 std::move 而延長。

總之: std::move 是一個有用的工具,但濫用它會適得其反,阻礙編譯器進行更高效的優化(復制省略),最終導致性能下降和潛在問題。在簡單返回局部變量和直接初始化場景中,應首選簡潔寫法,信任編譯器優化。







原文翻譯

The C++ std::move function casts its parameter to an rvalue reference, which enables its contents to be consumed by another operation. But in your excitement about this new expressive capability, take care not to overuse it.

C++ std::move 函數將其參數強制轉換為右值引用,從而使其內容可供其他作使用。但是,在您對這種新的表達能力感到興奮時,請注意不要過度使用它。

std::string get_name(int id)
{std::string name = std::to_string(id);/* assume other calculations happen here 假設這里發生了其他計算*/return std::move(name);
}

You think you are giving the compiler some help by saying “Hey, like, I’m not using my local variable name after this point, so you can just move the string into the return value.”

您認為您通過說“嘿,比如,在這一點之后我沒有使用我的局部變量名稱 ,所以你可以將字符串移動到返回值中”來為編譯器提供一些幫助。

Unfortunately, your help is actually hurting. Adding a std::move causes the return statement to fail to satisfy the conditions for copy elision (commonly known as Named Return Value Optimization, or NRVO): The thing being returned must be the name of a local variable with the same type as the function return value.

不幸的是,你的幫助實際上是有害的。添加 std::move 會導致 return 語句無法滿足復制省略的條件 (通常稱為命名返回值優化,或 NRVO):返回的事物必須是與函數返回值類型相同的局部變量的名稱。

The added std::move prevents NRVO, and the return value is move-constructed from the name variable.

添加的 std::move 會阻止 NRVO,并且返回值是從 name 變量移動構造的。

std::string get_name(int id)
{std::string name = std::to_string(id);/* assume other calculations happen here 假設這里發生了其他計算*/return name;
}

This time, we return name directly, and the compiler can now elide the copy and put the name variable directly in the return value slot with no copy. (Compilers are permitted but not required to perform this optimization, but in practice, all compilers will do it if all code paths return the same local variable.)

這一次,我們直接返回 name,編譯器現在可以省略 copy 并將 name 變量直接放在沒有 copy 的返回值槽中。(允許但不要求編譯器執行此優化,但實際上,如果所有代碼路徑都返回相同的局部變量,則所有編譯器都會執行此作。

The other half of the overzealous std::move is on the receiving end.

過分熱心的 std::move 的另一半在接收端。

extern void report_name(std::string name);void sample1()
{std::string name = std::move(get_name());
}void sample2()
{report_name(std::move(get_name()));
}

In these two sample functions, we take the return value from get_name and explicitly std::move it into a new local variable or into a function parameter. This is another case of trying to be helpful and ending up hurting.

在這兩個示例函數中,我們從 get_name 獲取返回值,并顯式地 std::move 將其放入新的局部變量或函數參數中。這是另一種試圖提供幫助但最終受傷的情況。

Constructing a value (either a local variable or a function parameter) from a matching value of the same type will be elided: The matching value is stored directly into the local variable or parameter without a copy. But adding a std::move prevents this optimization from occurring, and the value will instead be move-constructed.

從相同類型的匹配值構造值(局部變量或函數參數)將被省略:匹配值直接存儲到局部變量或參數中,無需復制。但是添加 std::move 會阻止這種優化的發生,并且該值將被 move 構造。

extern void report_name(std::string name);void sample1()
{std::string name = get_name();
}void sample2()
{report_name(get_name());
}

What’s particularly exciting is when you combine both mistakes. In that case, you took what would have been a sequence that had no copy or move operations at all and converted it into a sequence that creates two extra temporaries, two extra move operations, and two extra destructions.

特別令人興奮的是,當你把這兩個錯誤結合起來時。在這種情況下,您獲取了一個根本沒有復制或移動作的序列,并將其轉換為一個序列,該序列創建了兩個額外的臨時作、兩個額外的移動作和兩個額外的銷毀。

#include <memory>
struct S
{S();S(S const&);S(S &&);~S();
};extern void consume(S s); // 消耗// Bad version
S __declspec(noinline) f1()
{S s;return std::move(s);
}void g1()
{consume(std::move(f1()));
}

Here’s the compiler output for msvc:

以下是 msvc 的編譯器輸出:

; on entry, rcx says where to put the return value在入口處,rcx指出將返回值放在何處。
f1:mov     qword ptr [rsp+8], rcxpush    rbxsub     rsp, 48mov     rbx, rcx; construct local variable s on stack在堆棧上構造局部變量slea     rcx, qword ptr [rsp+64]call    S::S(); copy local variable to return value復制局部變量到返回值lea     rdx, qword ptr [rsp+64]mov     rcx, rbxcall    S::S(S &&); destruct the local variable s析構局部變量 slea     rcx, qword ptr [rsp+64]call    S::~S(); return the result返回結果mov     rax, rbxadd     rsp, 48pop     rbxretg1:sub     rsp, 40; call f1 and store into temporary variable調用f1并存儲到臨時變量中lea     rcx, qword ptr [rsp+56]call    f1(); copy temporary to outbound parameter復制臨時到出站參數mov     rdx, raxlea     rcx, qword ptr [rsp+48]call    S::S(S &&); call consume with the outbound parameter使用出站參數調用消費mov     rcx, raxcall    consume(S); clean up the temporary清理臨時的lea     rcx, qword ptr [rsp+56]call    S::~S(); returnadd     rsp, 40ret

Notice that calling g1 resulted in the creation of a total of two extra copies of S, one in f1 and another to hold the return value of f1.

請注意,調用 g1 會導致總共創建兩個額外的 S 副本,一個在 f1 中,另一個用于保存 f1 的返回值。

By comparison, if we use copy elision:

相比之下,如果我們使用 copy elision:

// Good version
S __declspec(noinline) f2()
{S s;return s;
}void g2()
{consume(f2());
}

then the msvc code generation is

那么 msvc 代碼生成是

; on entry, rcx says where to put the return value在入口處,rcx指出將返回值放在何處。
f2:push    rbxsub     rsp, 48mov     rbx, rcx; construct directly into return value (still in rcx)直接構造為返回值(仍在rcx中)call    S::S(); and return it并將其返回mov     rax, rbxadd     rsp, 48pop     rbxretg2:sub     rsp, 40; put return value of f1 directly into outbound parameter將f1的返回值直接放入出站參數中lea     rcx, qword ptr [rsp+48]call    f2(); call consume with the outbound parameter使用出站參數調用消費mov     rcx, eaxcall    consume(S); returnadd     rsp, 40ret

You get similar results with gcc, clang, and icc icx.

使用 gcc、clang 和 icc icx 可以得到類似的結果。

In gcc, clang, and icx, you can enable the pessimizing-move warning to tell you when you make these mistakes.

在 gcc、clang 和 icx 中,您可以啟用 pessimizing-move 警告,以便在您犯這些錯誤時通知您。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/84561.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/84561.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/84561.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Ubuntu24.04 onnx 模型轉 rknn

前面的環境配置有點懶得寫&#xff0c;教程也很多&#xff0c;可以自己找 rknn-toolkit2 gitee 地址&#xff1a;pingli/rknn-toolkit2 試了很多開源的代碼&#xff0c;都沒辦法跑通&#xff0c; 最后自己改了一版 微調后的 qwen2 模型適用 from rknn.api import RKNN impor…

Electron通信流程

前言 今天講Electron框架的通信流程&#xff0c;首先我們需要知道為什么需要通信。這得益于Electron的多進程模型&#xff0c;它主要模仿chrome的多進程模型如下圖&#xff1a; 作為應用開發者&#xff0c;我們將控制兩種類型的進程&#xff1a;主進程和渲染器進程 。 …

uni-app項目實戰筆記1--創建項目和實現首頁輪播圖功能

ps:本筆記來自B站咸蝦米壁紙項目 一.創建項目&#xff0c;完成項目初始化搭建 1.在HBuilder X創建wallper項目&#xff0c;使用默認模塊&#xff0c;選擇vue&#xff1b; 2.在項目根目錄下創建common目錄&#xff0c;用于存放靜態資源&#xff0c;創建項目時自動生成static目…

機械制造系統中 PROFINET 與 PROFIBUS-DP 的融合應用及捷米科技解決方案

在機械制造領域&#xff0c;工業通信網絡的兼容性與靈活性直接影響產線的自動化水平與生產效率。當前&#xff0c;多數機械制造系統采用PROFINET 控制器構建核心網絡架構&#xff0c;并通過微波無線連接實現設備互聯。隨著工業網絡的發展&#xff0c;系統中常需同時集成PROFINE…

MCP 協議系列序言篇:開啟 AI 應用融合新時代的鑰匙

文章目錄 序言&#xff1a;AI 應用層進入 MCP 時代為什么 MCP 開啟 AI 應用融合新時代的鑰匙為什么是 MCP&#xff1f;它與 Function Calling、Agent 有什么區別&#xff1f;Function CallingAI AgentMCP&#xff08;Model Context Protocol&#xff09; MCP 如何工作MCP Serve…

【threejs】每天一個小案例講解:光照

代碼倉 GitHub - TiffanyHoo/three_practices: Learning three.js together! 可自行clone&#xff0c;無需安裝依賴&#xff0c;直接liver-server運行/直接打開chapter01中的html文件 運行效果圖 知識要點 常見光照類型及其特點如下&#xff1a; 1. 環境光&#xff08;Ambi…

大模型在輸尿管下段積水預測及臨床應用的研究

目錄 一、引言 1.1 研究背景與意義 1.2 研究目的 1.3 研究范圍與限制 1.4 文獻綜述 1.5 研究方法和框架 二、相關理論與概念 2.1 大模型技術原理 2.2 輸尿管下段積水病理機制 2.3 大模型在醫學預測領域的應用 三、大模型預測輸尿管下段積水的方法 3.1 數據收集 3.…

gitlab相關操作

2025.06.11今天我學習了如何在終端使用git相關操作&#xff1a; 一、需要修改新的倉庫git地址的時候&#xff1a; &#xff08;1&#xff09;檢查當前遠程倉庫 git remote -v 輸出示例&#xff1a; origin https://github.com/old-repo.git (fetch) origin https://github.c…

51c自動駕駛~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention為LLM長文本建模帶來突破性進展 琶洲實驗室、華南理工大學聯合推出關鍵上下文感知注意力機制&#xff08;CCA-Attention&#xff09;&#xff0c;…

通過共享內存在多程序之間實現數據通信

注&#xff1a;以下內容為與 GPT-4O 共同創作完成 以共享內存的方式實現多程序之間的數據通信&#xff0c;尤其適合在一臺機器上的多程序之間進行高頻數據交換。 以下示例展示了 sender.py 向 receiver.py 發送數據并接收經 receiver.py 處理后的數據&#xff0c;以及如何通過…

[論文閱讀] 人工智能+軟件工程 | 理解GitGoodBench:評估AI代理在Git中表現的新基準

理解GitGoodBench&#xff1a;評估AI代理在Git中表現的新基準 論文信息 GitGoodBench: A Novel Benchmark For Evaluating Agentic Performance On Git Tobias Lindenbauer, Egor Bogomolov, Yaroslav Zharov Cite as: arXiv:2505.22583 [cs.SE] 研究背景&#xff1a;當AI走進…

開源 java android app 開發(十二)封庫.aar

文章的目的為了記錄使用java 進行android app 開發學習的經歷。本職為嵌入式軟件開發&#xff0c;公司安排開發app&#xff0c;臨時學習&#xff0c;完成app的開發。開發流程和要點有些記憶模糊&#xff0c;趕緊記錄&#xff0c;防止忘記。 相關鏈接&#xff1a; 開源 java an…

ubuntu + nginx 1.26 + php7.4 + mysql8.0 調優

服務器配置 8核 16G 查看內存 free -h nginx配置 worker_processes auto; # 自動檢測CPU核心數 worker_rlimit_nofile 65535; # 提高文件描述符限制 ? events {worker_connections 8192; # 每個worker的最大連接數multi_accept on; # 一次性接受…

[未驗證]abaqus2022 更改內置python

如何在 Abaqus 2022 中更改內置 Python 在 Abaqus 中&#xff0c;Python 是常用的腳本語言&#xff0c;它使得用戶能夠自動化模型的創建、分析和后處理。可能有時候你需要更改默認的 Python 版本&#xff0c;比如使用特定庫或者功能。本文將為您詳細說明如何在 Abaqus 2022 中更…

RAG文檔解析難點2:excel數據“大海撈針”,超大Excel解析與精準行列查詢指南

寫在前面 在構建檢索增強生成(RAG)應用時,Excel文件是不可或缺的數據源。它們通常包含了企業運營、市場分析、科學研究等各個領域的寶貴數據。然而,當這些Excel文件變得“超大”——可能包含數十萬甚至數百萬行數據時,傳統的解析方法和RAG數據處理流程將面臨嚴峻的內存、…

深度掌控,智啟未來 —— 基于 STM32F103RBT6 的控制板

在科技浪潮奔涌向前的時代&#xff0c;電子領域的創新發展從未停歇。對于電子工程師、科研工作者以及電子技術愛好者&#xff0c;在校電子專業學生而言&#xff0c;一款性能卓越、功能全面且穩定可靠的開發板&#xff0c;是探索電子世界奧秘、實現創意構想的關鍵基石。今天&…

什么樣的登錄方式才是最安全的?

目錄 一、基礎協議&#xff1a;HTTP與HTTPS HTTP協議 HTTPS協議 二、常見Web攻擊與防御 2.1 XSS 常見攻擊手段 針對XSS 攻擊竊取 Cookie 2.2 CSRF CSRF攻擊的核心特點 與XSS的區別 常見防御措施 三、疑問解答 四、登錄方式演變 4.1 方案一&#x1f436;狗都不用 …

android studio底部導航欄

實現底部導航欄切換 將java文件return的xml文件賦值給頁面FrameLayout控件 java文件BottomNavigationView&#xff0c;監聽器setOnNavigationItemSelectedListener MainActivity.java代碼 package com.example.myapplication;import android.os.Bundle;import androidx.appc…

vue-router相關理解

一、前言 隨著 Vue.js 在前端開發中的廣泛應用&#xff0c;Vue Router 成為了 Vue 官方推薦的路由管理器。它不僅支持單頁面應用&#xff08;SPA&#xff09;中常見的路由跳轉、嵌套路由、懶加載等功能&#xff0c;還提供了導航守衛、動態路由等高級特性。 本文將帶你深入了解…

uni-app 自定義路由封裝模塊詳解(附源碼逐行解讀)

&#x1f680;uni-app 自定義路由封裝模塊詳解&#xff08;附源碼逐行解讀&#xff09; &#x1f4cc; 請收藏 點贊 關注&#xff0c;獲取更多 uni-app 項目實用技巧&#xff01; 在實際 uni-app 項目中&#xff0c;我們常常需要對 uni.navigateTo、uni.switchTab 等 API 做…