在c++中,可以使用一個分配了內存空間但是沒有構造的struct里的值類型變數嗎?
在實現某些數據結構時,會需要一個哨兵節點,但是不想構造該節點,因為構造時會連著裡面的一些大對象一起構造,而我們其實只需要使用該節點中的一些布爾變數之類的值類型變數。那麼問題來了,這是合法的還是ub? 比如說這樣子的紅黑樹節點:
template&
struct node
{
T val;
node* left;
node* right;
node* parent;
bool color;
node( const T v ) : val(v) {}
};
不想為哨兵節點構造val,一方面是出於代價的考慮, 另一方面,如果去構造它的話,還必須要求T有默認構造函數,因為需要構造各種不同的類型T
你確定這不是一個 X-Y 問題?因為具體到 sentinel 的情況,有一個常用做法,就像上面有人提到的,繼承 node_base. 這是 libc++ 片段: llvm-mirror/libcxx
注意把 destructor 刪掉而不是使用 virtual destructor. 因為在使用 node 的數據結構中「正確地」使用析構函數就意味著你需要 N 層高的棧,list 肯定不能這麼干那麼 tree 也沒必要這麼干,肯定是手動回收節點,所以同時手動析構掉 val 不成問題。
玩火自焚呀,這證明你的類型設計得還不夠精細。一個好的設計,在類型上看可以完全地反映你的需求(不會出現有些地方永遠都不會用到的情況),而且還不會給你造成煩惱(譬如說你有一些隱含的知識,知道這裡要cast成什麼然後如何如何用)。
用一個比較弱的方法
template&
struct maybe_sentinel
{
union _bx{
char sentinel[sizeof(T)];
T val;
} bx;
};
因為哨兵節點只有可能出現在哨兵位置,所以也沒必要給union加tag,依照上下文選擇union成員就行了,只是你需要手動釋放union成員
想到兩種解決方案:
1.把T變成unique_ptr&在現代stl各版本中,一般的做法不都是有個node_base 然後node繼承base么
你寫了 T val; 的話基本上不想構造也得構造。
http://en.cppreference.com/w/cpp/utility/optional
要不考慮把 T val; 換成 std::optional&
或手動把 optional 的標籤和 color 整合在一起,其他地方手寫 optional 的實現。
(寫個 alignas(T) char val_buf[sizeof(T)] 佔好空間,有需要時才在上面布置 new 。寫兩個 get_value() 把它 reinterpret_cast 成 T 的引用)
若你的 T 有輕量級構造函數和表示空狀態的值的話,就不用這麼麻煩了。
不知道,雖然感覺不UB可能性比較大,但是即便不UB你也很容易把自己玩死。還是明確區分一下吧,比較保險又不太麻煩的辦法還是有很多的。
----
呃,你說不構造指根本不給分配完整的內存,還是單純不構造某些成員?如果是後者你確定另外定義一套給哨兵節點用的構造邏輯不上算么?
改成T* val,重載一下構造函數,哨兵不new不就好了。
寫個包裝類就可以了
template&
class LazyConstrucor
{
char data[sizeof(T)];
bool init;
public:
LazyConstructor() { init = false; }
operator T() {
if(!init) {
new (data)T;
init = true;
}
return *(T*)data;
}
};
template&
struct node
{
LazyConstructor&
node* left;
node* right;
node* parent;
bool color;
T getVal(){return val;}
//node( const T v ) : val(v) {} 忽然發現這裡有個拷貝構造,那延遲構造是不可能的,因為沒法保證傳入的v在延遲構造的時候還存在
};
有個類似的做法是placement new。但不推薦用在你這種情況。
從語言層面來說沒問題,但建議把這種行為反映在struct中,就算只是union也好,就算是C,不到必要的時候也不要不cast強行湊binary玩
推薦閱讀:
※c++里,函數返回一個局部數組名可以嗎?
※C++中String問題?
※請問我該注重學習Linux哪個方面?
※python調用CC++的方法各有什麼優勢,哪個最好?
※我寫C++喜歡用繼承有問題么?