0%

Empty struct, empty_value and [[no_unique_address]]

之前遇到亂流,需要重新找工作,如今告一段落,可以寫點東西了

來聊聊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::lessstd::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)
> //2
, 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)
> //2
, 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)
> //2
, 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