之前遇到亂流,需要重新找工作,如今告一段落,可以寫點東西了
來聊聊Empty Struct的問題好了
Empty struct
1 2 3
| struct empty { }; printf("%ld\n", sizeof(struct empty));
|
這個答案有所不同
在C語言,印出來的情形是0
在C++,印出來會是1,C++為了保證不同的Object的Address不同,就算是empty struct, sizeof也不為空
Embedded empty struct
那如果是這樣呢
1 2 3 4 5 6 7 8
| struct empty { };
struct non_empty { int v; struct empty e; }; printf("%ld\n", sizeof(struct non_empty));
|
同樣的
在C語言,印出來的情形是4
在C++,印出來當然不會是4,在我的ubuntu 64bit印出來是8
Why use empty struct
在C語言的應用情景,empty struct沒有任何用途
可是在C++的世界裡面,empty struct可以是個functor
例如std::less
,std::equal_to
之類的
使用template class可以將functor傳入struct裡面,因此可以擴充這個class的功能
How to reuduce the size
既然有empty struct的使用場景,又不想浪費多餘的空間,所以就有人想出這樣的方法
1 2 3 4
| struct non_empty : empty { int v; }; printf("%ld\n", sizeof(struct non_empty));
|
這下就如我們預料的是4了
The problem of Inherence
Leak Interface
由於Inherence有很強的傳染力,Parent class的Public API都能背Child class自由使用,因此可以寫出這樣的程式碼
1 2 3 4 5 6 7 8 9
| class empty { public: int f() { return 42; } }; class non_empty : public X { int v; }; non_empty obj; obj.f();
|
可是我不想讓obj直接f函數…該怎麼做
1 2 3 4 5 6 7 8 9 10 11 12 13
| class empty { public: int f() { return 42; } }; class X : empty { public: empty& get() { return *this; }; }; class non_empty : public X { int v; }; non_empty obj; obj.get().f();
|
要使用f只能透過get來做了
這也是boost empty_value
在做的事情
Hard to reason sometimes
這也是我看了程式碼才能體會到的事情
以下是從boost intrusive
中節錄的片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| template<class ValueTraits, class VoidOrKeyOfValue, class VoidOrKeyHash, class VoidOrKeyEqual, class BucketTraits, class SizeType, std::size_t BoolFlags> struct hashdata_internal : public hashtable_size_traits_wrapper < bucket_hash_equal_t < ValueTraits, VoidOrKeyOfValue, VoidOrKeyHash, VoidOrKeyEqual , BucketTraits , 0 != (BoolFlags & hash_bool_flags::cache_begin_pos) > , SizeType , (BoolFlags & hash_bool_flags::incremental_pos) != 0 > { typedef hashtable_size_traits_wrapper < bucket_hash_equal_t < ValueTraits, VoidOrKeyOfValue, VoidOrKeyHash, VoidOrKeyEqual , BucketTraits , 0 != (BoolFlags & hash_bool_flags::cache_begin_pos) > , SizeType , (BoolFlags & hash_bool_flags::incremental_pos) != 0 > internal_type;; };
|
hashtable_size_traits_wrapper依設定不同,可能是個empty struct
上面這段,重歷的程式碼出現了兩次,又臭又長,難以理解
[[no_unique_address]]
C++20引進了一個很有用的attribute,這告訴Compilier,不必為這個object特別分配一個Address,因此有了無限可能
No need inherence
雲本需要用Inherence辦到的事情
1 2 3 4 5 6 7 8 9 10 11 12 13
| class empty { public: int f() { return 42; } };
class non_empty { int v; [[no_unique_address]] empty e; public: empty& get() { return e; } }; non_empty obj; obj.get().f();
|
Fix hard to reason issue
以上面那段Code來舉例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| template<class ValueTraits, class VoidOrKeyOfValue, class VoidOrKeyHash, class VoidOrKeyEqual, class BucketTraits, class SizeType, std::size_t BoolFlags> struct hashdata_internal { typedef hashtable_size_traits_wrapper < bucket_hash_equal_t < ValueTraits, VoidOrKeyOfValue, VoidOrKeyHash, VoidOrKeyEqual , BucketTraits , 0 != (BoolFlags & hash_bool_flags::cache_begin_pos) > , SizeType , (BoolFlags & hash_bool_flags::incremental_pos) != 0 > internal_type;; [[no_unique_address]] internal_type size_traits_; };
|
雖然還是又臭又長,不過已經改善不少
Trap on [[no_unique_address]]
以下的程式碼會是如何
1 2 3 4 5 6 7 8
| class empty { }; class non_empty { int v; [[no_unique_address]] empty e; [[no_unique_address]] empty e1; }; printf("%ld\n", sizeof(struct non_empty));
|
答案當然不是4,e和e1是同一個type,為了區分,不所以只能有一個有[[no_unique_address]]的屬性
要修掉這問題也很簡單
1 2 3 4 5 6 7 8 9
| template <int> class empty { };
class non_empty { int v; [[no_unique_address]] empty<0> e; [[no_unique_address]] empty<1> e1; };
|
Another issue on [[no_unique_address]]
目前[[no_unique_address]]在MSVC是沒效果的,會造成ABI Break