感覺有兩點吧,第一是C++記憶體管理複雜,設計成引用語義萬一出現迴圈引用怎麼辦,它在設計之初並沒有GC(雖說現在也沒有,,,)出現這種情況不好解決。第二是C++有指標和引用,需要傳遞引用的時候可以用這兩者解決,所以設計成現在這個模式更加方便而且也不影響功能。
因為題主所說的“其他語言”基本上不需要使用者進行記憶體管理。
C++ 需要使用者掌控某種型別的物件是否允許多個物件共享一個,所以智慧指標才會有
unique_ptr
和
shared_ptr
兩種。一個物件被多個 owner 引用,GC 語言用慣了會覺得這就是個語言必備的 feature,無可厚非。但是呢,底層至少有個引用計數的加減問題,這個效能開銷 C++ 需要使用者顯式地註明:我知道這個開銷,我願意承擔。
比如,一個
vector
在可見範圍內,被多個函式共享,你可以傳遞引用。
void
g
(
std
::
vector
<
int
>&
v
)
{
/* 。。。 */
}
void
f
()
{
std
::
vector
<
int
>
v
{
1
,
2
,
3
};
// f 對 v 進行一些操作
g
(
v
);
// g 對 v 進行另一些操作
}
沒有任何額外開銷,只有引用的傳遞(指向
vector
的指標的複製)。
問題來了,事情並不會總是這麼簡單,假如函式
g
是開啟一個執行緒去處理
v
,那麼有可能線上程函式中訪問
v
時,
f
已經返回,
v
離開作用域被析構,此時訪問
v
是不合法的。
在這裡,你可以使用換個思路,使用移動語義轉移控制權,延長
vector
的生命週期到執行緒函式結束。
void
threadFunc
(
std
::
vector
<
int
>&&
v
)
{
/* 。。。 */
}
void
g
(
std
::
vector
<
int
>&
v
)
{
std
::
thread
task
{
threadFunc
,
std
::
move
(
v
)};
// C++14 可以直接傳 lambda 來捕獲右值引用
task
。
detach
();
}
void
f
()
{
std
::
vector
<
int
>
v
{
1
,
2
,
3
};
// f 對 v 處理完畢後交給 g
g
(
v
);
// 傳給 g 之後,f 不能再處理 v 了
}
當然,你不在意開銷的話,可以這麼搞:
using
VectorInt
=
std
::
vector
<
int
>
;
using
SharedVectorInt
=
std
::
shared_ptr
<
VectorInt
>
;
void
g
(
SharedVectorInt
pv
)
{
std
::
thread
task
([
pv
]
{
/* 。。。 */
});
task
。
detach
();
}
void
f
()
{
SharedVectorInt
pv
(
new
VectorInt
{
1
,
2
,
3
});
// f 可以在呼叫 g 之前處理
g
(
pv
);
// f 也可以在呼叫 g 之後處理
}
開銷有哪些?首先
vector
本身是分配在堆上的,涉及一次小記憶體的申請(內部的三個指標)。其次,
shared_ptr
內部維護了引用計數,會涉及到若干次引用計數的增加和減少,引用計數基本上是
atomic
型別,自增和自減開銷都比一般的整型大。
但實際上呢,很多時候這點開銷相比整個處理流程的開銷,算不上什麼。所以我為何不這麼寫呢?
void
f
(
List
<
Integer
>
list
)
{
/* 。。。 */
g
(
list
);
/* 。。。 */
}
void
g
(
List
<
Integer
>
list
)
{
/* 。。。 */
}
再回頭來看,如果
vector
的複製變成淺複製,也就是所謂的引用語義,那麼你要考慮:這個引用,要不要用一個引用計數來維護?
不要:存在生命週期的問題,使用者很容易瞎用導致崩。(雖然本來很多寫慣了其他語言的來寫 C++ 就容易崩)
要,OK,那我就是想要壓榨效能,就是不想要引用計數增減的開銷,我要怎麼做?傳遞
vector
當作開銷小的引用,傳遞
vector
當作開銷大的引用?你覺得這樣設計好嗎?
跟題主的理解恰恰相反,通常值語義的效能會更好。這是因為值語義會使資料結構的記憶體佈局更緊湊,更能讓編譯器和cache發揮作用,而這是引用語義無法做到的。例如陳碩在其部落格上有文章進行了講述,知乎上也有不少文章和回答進行了闡述。
至於所謂的“深複製”往往不是必然的,程式設計師有足夠的的決定權來選擇“深”與“淺”。
其他語言用引用語義是因為只有引用語義……
你以為引用語義的語言就是記憶體佈局變一下,實際上還多了GC,對效能的影響就不像C++那麼好控制了