標籤:

在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&,也就是變成指針。

2.從node_base基類派生出node_with_val和node_empty。不過這樣一來的話得用虛虛構函數保證node_with_val銷毀時正確釋放val(得靠RTTI),或者的話就是自己在node_base里加入標識區分有無val(這不是跟方案一差不多了嗎)。

不知道有沒有更好更恰當的解決方案。


在現代stl各版本中,一般的做法不都是有個node_base 然後node繼承base么


你寫了 T val; 的話基本上不想構造也得構造。

http://en.cppreference.com/w/cpp/utility/optional

要不考慮把 T val; 換成 std::optional& val; ,舊標準可以弄個 boost 版本的。

或手動把 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& val;
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++喜歡用繼承有問題么?

TAG:C | CC |