您是使用boost::variant
還是C ++項目中“標記的Union”或變體類型的許多開源C ++的實現之一?
boost::variant
是一個很棒的圖書館。我創建了strict_variant
以解決我不喜歡的boost::variant
的一些事情。
TL; dr版本是與boost::variant
或std::variant
不同, strict_variant
永遠不會拋出異常或在支持具有投擲動作的類型的努力中進行動態分配。如果發生這種情況,默認版本將使靜態斷言失敗。 strict_variant::easy_variant
將在這種情況下進行分配,因此,如果需要,您可以選擇加入,這兩個版本的變體“很好地”一起播放。這種事情通常是具有實時需求或嵌入式設備的項目中的主要問題,或者可能不允許使用這些C ++功能。如果您正在製作可能在“常規”項目中使用的庫,這些庫希望來自boost::variant
易用性,但也可以用於具有限制性要求的項目中,並且您想使用variant
類型作為API的一部分, strict_variant
可能會提供一種使每個人都開心的方法。
除此之外,在變體的界面中還有一些問題,這些問題使使用日常的恕我直言更加愉快。 (這些實際上是該項目的最初動機。)
我不喜歡這樣的代碼可能會編譯而沒有任何警告或錯誤消息:
boost::variant<std::string, int > v;
v = true ;
我通常寧願我的variant
對可能發生的隱性轉換更加限制。
我希望這樣的事情應該編譯並做有意義的事情,即使解決方案是模棱兩可的。
variant< bool , long , double , std::string> v;
v = true ; // selects bool
v = 10 ; // selects long
v = 20 . 5f ; // selects double
v = " foo " ; // selects string
我還希望這種行為(在這種情況下選擇)是便攜式的。
(對於這樣的代碼示例, boost::variant
具有不幸的行為,請參見文檔中的“抽象和動機”。)
在strict_variant
中,我們通過刪除某些候選人來修改這些情況下的過載分辨率。
例如:
bool
,積分,浮點,指針,角色和其他一些類之間的標準轉換。int -> long
and int -> long long
是候選人,則消除了long long
。有關詳細信息,請參見文檔。
我不喜歡那個boost::variant
默默地製作我的物體的備份副本。例如,考慮這個簡單的程序,其中已定義了A
和B
來記錄所有CTOR和DTOR調用。
int main () {
using var_t = boost::variant<A, B>;
var_t v{ A ()};
std::cout << " 1 " << std::endl;
v = B ();
std::cout << " 2 " << std::endl;
v = A ();
std::cout << " 3 " << std::endl;
}
boost::variant
產生以下輸出:
A ()
A(A&&)
~A()
1
B()
B(B&&)
A( const A &)
~A()
B( const B &)
~A()
~B()
~B()
2
A()
A(A&&)
B( const B &)
~B()
A( const A &)
~B()
~A()
~A()
3
~A()
對於某些程序員來說,這可能會令人驚訝。
相比之下,如果您使用C ++ 17 std::variant
或具有“有時偏置”語義的變體,則會得到類似的東西(此std::experimental::variant
的輸出)
A ()
A(A&&)
~A()
1
B()
~A()
B(B&&)
~B()
2
A()
~B()
A(A&&)
~A()
3
~A()
這更接近天真的程序員期望的人不了解boost::variant
內部細節,這是他在源代碼中看到的唯一存在對象的副本。
這種事情通常無關緊要,但是有時,如果例如,如果您要調試一個討厭的內存損壞問題(也許在變體中包含的對象之一中存在不良代碼),那麼這些額外的對象,移動和復制,可能使事情變得更加複雜。
這是您使用strict_variant
所獲得的:
A ()
A(A&&)
~A()
1
B()
B(B&&)
~A()
~B()
2
A()
A(A&&)
~B()
~A()
3
~A()
但是, strict_variant
沒有空的狀態,並且是完全例外的!
(這些示例來自gcc 5.4
,請參見example
文件夾中的代碼。)
總結差異:
std::variant
很少發行,總是基於堆棧。實際上,當拋出異常時,這是空的。稍後,如果您在空白時嘗試訪問,它會引發不同的異常。boost::variant
永遠是不空的,通常是基於堆棧的。只要能夠拋出異常,它就必須進行動態分配和備份副本,但是如果實際上並未拋出異常,則會立即被釋放。strict_variant
永遠是不空的,噹噹前值類型不可移動時,請完全基於堆棧。它永遠不會進行備份移動或複制,也從未引發過例外。每種方法都有其優點。我選擇了strict_variant
方法,因為我發現它更簡單,並且避免了我認為是boost::variant
and std::variant
的缺點。而且,如果您設法使所有類型都無法構造,我經常發現我可以構造,那麼strict_variant
可以為您提供最佳性能,與std::variant
相同,而沒有空狀態。
有關設計的深入討論,請查看文檔。
有關變體的溫和介紹以及嚴格變化的概述,請參閱我對此進行的演講中的幻燈片:[PPTX] [PDF]
在github頁面上。
strict_variant
針對C ++ 11標準。
眾所周知,它可以與gcc >= 4.8
一起使用,並且clang >= 3.5
,並針對MSVC 2015
進行了測試。
strict_variant
可以在需要-fno-exceptions
和-fno-rtti
的項目中使用。
在Boost軟件許可證下可用嚴格的變體。