狂野西部编码:E.B.C.O. 压缩






4.64/5 (3投票s)
将压缩对重写为类型列表
引言
如果您曾经需要将一个或多个类型占用的空间降至最低,那么您可能已经考虑过 std::ebco 基类或压缩对。两者都被划线表示仅供内部使用,可能会发生更改,不面向公众消费,仅凭这一点它们就不理想。此外,包含的类型不止一对,会导致一系列冗长的函数调用链和成员访问。因此,需要考虑哪些类型应该被压缩成第二个,哪些应该被压缩成第一个。这需要对压缩本身有深入的了解,才能布局一个能够实现最高效打包安排的格式。可变长度、压缩的类型列表的创建旨在解决这些问题。
背景
空基类优化是针对没有非静态成员或虚函数的类型执行的一种优化。通常,任何对象的最小大小必须为一,以保证同一类型但不同的对象始终是不同的。但这不适用于基类对象,允许其大小被完全优化掉。这可以作为一种压缩技术,类似于 boost::compressed_pair。
设计
压缩类型的通用设计是一个类,它接受可变数量的类型,一个或多个,并且只暴露一种类型。该类型将只公开一个成员。该成员将是一个函数调用 get
。它将接受一个类型作为模板参数,并返回该类型的引用。该引用将能够从 get 调用中赋值,或赋值到 get 调用中。get 调用不会产生函数调用开销,并且与使用引用变量至关重要。compressed 暴露的类型可以通过接受每个类型值的构造函数,或默认构造每个给定类型的默认构造函数来构造。提供的所有错误处理都将以编译时错误消息的形式出现,并且是完整的。也就是说,如果它在没有错误的情况下编译,那么它就应该在没有错误的情况下运行。
我们要做的第一件事是设置压缩类型的定义,它将是一个类,因为它们默认是 private
,并设置一些错误条件。其中第一个是使用 static if
(if constexpr
) 的设计选择。它的使用不是绝对必要的,但可以节省大量辅助类型,并使代码在含义上更清晰。对于不熟悉 if constexpr
的人来说,只有在静态求值为 true
的表达式的作用域内的代码才会被编译。这是 C++17 语言扩展,其使用将代码限制在最低限度。如果此解决方案在早期版本的语言中需要,我将很乐意根据需要修改代码。对于我们的宏评估错误和警告,我们将使用一个特性,其代码 在此处 进行了说明。这还将为我们提供一种激进的 constexpr
和 inline
形式。这些将确保我们的类型的构造、析构和使用不会产生任何开销。这些也在上一篇文章中进行了说明,超出了本文的范围。除了这个错误,我们将设置一个空类型错误。与其处理一个空的压缩对象,不如直接禁止它。
template<typename... Types>
class compressed
{
#if !defined(__cpp_constexpr)
JOE((error("C++ if constexpr required.")))
#elif defined(__cpp_constexpr) && !(__cpp_constexpr >= 201603L)
# ifndef COMPRESSED_SILENCE_STATIC_IF_WARNING
JOE((warning("C++ if constexpr required,
check your compiler's documentation for availability.
Silence with : COMPRESSED_SILENCE_STATIC_IF_WARNING")))
# endif // COMPRESSED_SILENCE_STATIC_IF_WARNING
#endif
static_assert((sizeof...(Types) > 0),
"The given types for compressed must not be empty");
所有辅助类型和特性都将包含在 compressed 类中。由于解决方案的一部分依赖于类型转换来调用函数,因此这些类型必须是 public
才能访问调用。我们可以使用默认为 public
的 struct
,并允许该类型的每个成员都为 public
,以简化此操作。所有这些辅助类型尽管在定义中是 public
的,但从 compressed 外部访问它们将是 private
的。这将确保它们不会被滥用。
template <class _Ty>
using remove_cvref_t = typename std::remove_cv
<typename std::remove_reference<_Ty>::type>::type;
template <typename Type, typename... Types>
using is_any_of = std::disjunction<std::is_same<Type, Types>...>;
template <typename _Type, typename _Other>
using EnableIf = typename std::enable_if<!std::is_same
<remove_cvref_t<_Other>, _Type>::value, int>::type;
template< typename ..._Types>
struct AutoPair;
template< typename ..._Types>
class PairCat;
我们将创建的第一个辅助类型是 EbcoBase struct
。该类型的任务是决定给定类型是否可以受益于空基类优化,并在任何一种情况下提供单一接口。它与 std::ebco 基类不同,因为它提供了一个默认构造函数,这是提供压缩类型的默认构造所必需的。添加了复制构造函数和移动构造函数,这是转发给定值沿构造函数链所必需的。值构造函数受到 enable_if
的保护,该保护用于确保它不会被用作复制构造函数或移动构造函数。它还有一个 static assert
来在提供错误的类型值进行构造时阐明构造函数问题。这似乎也改进了编译器产生的一些错误。在继承自它的其他类型中,我们只需转发可变数量的参数以简化构造函数错误消息。所有成员都用 constexpr
特性装饰,析构函数除外。这在 C++20 及更高版本中可以是 constexpr
,但我们将 C++17 作为我们的限制因素。因此,我们使用强制内联。即便如此,只有单个类型的构造函数和析构函数会运行。我们的辅助类型构造函数和析构函数不会运行。我们唯一的 get_value
调用提供了 const
和非 const
重载。我们所有的辅助类型都将遵循此模式。
template<typename _Type, bool =
!std::is_final<_Type>::value && std::is_empty<_Type>::value >
struct EbcoBase : private _Type
{ // Empty Base Class Optimization, active
JOE((constexpr)) EbcoBase() noexcept = default;
JOE((inline)) ~EbcoBase() noexcept = default;
template <class _Other, EnableIf<EbcoBase, _Other> = 0>
JOE((constexpr))
explicit EbcoBase(_Other&& _Val) noexcept
(std::is_nothrow_constructible<_Type, _Other>::value)
: _Type(std::forward<_Other>(_Val)) {
static_assert(std::is_constructible<_Type, _Other>::value,
"The given type can not be constructed with the provided value");
}
JOE((constexpr)) EbcoBase(const EbcoBase&) noexcept = default;
JOE((constexpr)) EbcoBase(EbcoBase&&) noexcept = default;
JOE((constexpr)) _Type& get_value() noexcept { return *this; }
JOE((constexpr)) const _Type& get_value() const noexcept { return *this; }
};
template<typename _Type>
struct EbcoBase<_Type, false>
{ // Empty Base Class Optimization, inactive
JOE((constexpr)) EbcoBase() noexcept = default;
JOE((inline)) ~EbcoBase() noexcept = default;
template <class _Other, EnableIf<EbcoBase, _Other> = 0>
JOE((constexpr))
explicit EbcoBase(_Other&& _Val)
noexcept(std::is_nothrow_constructible<_Type, _Other>::value)
: value(std::forward<_Other>(_Val)) {
static_assert(std::is_constructible<_Type, _Other>::value,
"The given type can not be constructed with the provided value");
}
JOE((constexpr)) EbcoBase(const EbcoBase&) noexcept = default;
JOE((constexpr)) EbcoBase(EbcoBase&&) noexcept = default;
JOE((constexpr)) _Type& get_value() noexcept { return value; }
JOE((constexpr)) const _Type& get_value()
const noexcept { return value; }
private:
_Type value;
};
我们构建的下一个类型 AutoPair
将负责使用多重继承将 EbcoBases 粘合在一起形成一个单一类型。它公开了一个单一的调用 get_type
,其任务是构建一个到 get_type
模板参数给定的类型的相应 EbcoBase 的转换。这会调用匹配的 EbcoBase 中相应的 get_value
调用,返回该类型的正确引用。如果给定的类型不是对创建的类型之一,则转换将失败。我们暂时不保护这一点,因为上层还会进行其他调用。
虽然使用相同类型进行多重继承没有问题,但在检索类型值时会出现一个问题。组合到 EbcoBase 的转换,对于任何重复出现的类型,它将转换为相同的类型,并返回相同的值。因此,我们禁止重复类型,因为第二或更多实例将无法通过此方法检索。static assert
确保编译会因重复类型而中断。
我们可以直接暴露 AutoPair
并将其用作最终类型,因为它能够处理无限数量的类型。但是,对于我们继承的每个空类,在第一个之后,编译器将回退到为每种类型分配至少一个字节,以确保每个 subobject
保持独特。由于 ebco 压缩受打包和对齐说明符的影响,这可能会添加任何空类所需的最小说明符大小。例如,假设我们有一个字节对齐和一个打包。我们有三种类型,两个空类和一个指针。在 x64 上,指针是八个字节,我们的第一个空类将受益于 ebco,不增加大小,第二个将增加一个字节。这加起来是九个。如果对齐只在一个类上指定为八个字节,那么我们的总大小将从九个字节跳到十六个字节。因为继承是多重继承,所以对齐说明符将由 compressed_t type
继承。打包说明符为八个字节会产生相同的影响,因为它会像对齐说明符一样添加填充。这就是为什么压缩超过两种类型需要制作类型和对的配对。稍后将详细介绍。
template<typename _Type>
struct AutoPair<_Type> : protected EbcoBase<_Type>
{
JOE((constexpr)) AutoPair() noexcept = default;
JOE((inline)) ~AutoPair() noexcept = default;
JOE((constexpr)) AutoPair(const AutoPair&) noexcept = default;
JOE((constexpr)) AutoPair(AutoPair&&) noexcept = default;
template <class _Other, EnableIf<AutoPair, _Other> = 0>
JOE((constexpr)) explicit AutoPair(_Other&& _Arg)
noexcept(std::is_nothrow_constructible<EbcoBase<_Type>, _Other>::value)
: EbcoBase<_Type>(std::forward<_Other>(_Arg)) {};
template<typename Type>
JOE((constexpr)) Type& get_type() noexcept {
return static_cast<EbcoBase<Type>*>(this)->get_value();
}
template<typename Type>
JOE((constexpr)) const Type& get_type() const noexcept {
return static_cast<const EbcoBase<Type>*>(this)->get_value();
}
};
template<typename _Type, typename ..._Types>
struct AutoPair<_Type, _Types...> :
protected EbcoBase<_Type>, public AutoPair< _Types...>
{
static_assert(!is_any_of<_Type, _Types...>::value,
"The given types for compression must be unique.");
JOE((constexpr)) AutoPair() noexcept = default;
JOE((inline)) ~AutoPair() noexcept = default;
JOE((constexpr)) AutoPair(const AutoPair&) noexcept = default;
JOE((constexpr)) AutoPair(AutoPair&&) noexcept = default;
template <typename _Other, typename ..._Others, EnableIf<AutoPair, _Other> = 0>
JOE((constexpr)) explicit AutoPair(_Other&& _Arg, _Others&&... _Args)
noexcept(std::is_nothrow_constructible<EbcoBase<_Type>, _Other>::value &&
std::is_nothrow_constructible<AutoPair< _Types...>, _Others...>::value)
: EbcoBase<_Type>(std::forward<_Other>(_Arg))
, AutoPair< _Types...>(std::forward<_Others>(_Args)...) {};
template<typename Type>
JOE((constexpr)) Type& get_type() noexcept {
return static_cast<EbcoBase<Type>*>(this)->get_value();
}
template<typename Type>
JOE((constexpr)) const Type& get_type() const noexcept {
return static_cast<const EbcoBase<Type>*>(this)->get_value();
}
};
现在我们有了一种按类型检索值的方法,并且自动应用了适当的压缩。接下来,我们需要合并无限数量的对,并提供一个统一的访问点。这是 PairCat
的责任。它将公开 get
函数调用作为访问类型值的手段。对于单个类型,我们直接继承自 EbcoBase
。我们的 get
调用构建到 EbcoBase 指针的 static cast
并调用 get_value
,返回给定类型的值。在此层,我们通过 static assert
保护任何转换问题,这也有助于阐明类型失败的原因。
template<typename _Type>
class PairCat<_Type> : private EbcoBase<_Type>
{
public:
JOE((constexpr)) PairCat() noexcept = default;
JOE((inline)) ~PairCat() noexcept = default;
JOE((constexpr)) PairCat(const PairCat&) noexcept = default;
JOE((constexpr)) PairCat(PairCat&&) noexcept = default;
template <class _Other, EnableIf<PairCat, _Other> = 0>
JOE((constexpr)) explicit PairCat(_Other&& _Arg)
noexcept(std::is_nothrow_constructible<EbcoBase<_Type>, _Other>::value)
: EbcoBase<_Type>(std::forward<_Other>(_Arg)) {};
template<typename Type>
JOE((constexpr)) Type& get() noexcept {
static_assert(std::is_same<Type, _Type>::value,
"The given type for get must be one of the compression types.");
return static_cast<EbcoBase<Type>*>(this)->get_value();
}
template<typename Type>
JOE((constexpr)) const Type& get() const noexcept {
static_assert(std::is_same<Type, _Type>::value,
"The given type for get must be one of the compression types.");
return static_cast<const EbcoBase<Type>*>(this)->get_value();
}
private:
template<typename...>
friend class PairCat;
};
对于两个对象,我们只需继承自 AutoPair
并将 get_type
的调用包装在 get
中。我们私有继承 AutoPair
,切断了继承链中到 get_type
和 get_value
的所有调用,只留下 get
作为访问手段。此模板与自身成为 friend,以允许其他特化访问该特定特化的此特定 get_type
调用,因为它们都将成为彼此的 friend。
template<typename _Ty1, typename _Ty2>
class PairCat< _Ty1, _Ty2> : private AutoPair<_Ty1, _Ty2>
{
static_assert(!std::is_same<_Ty1, _Ty2>::value,
"The given types for compression must be unique.");
using Base = AutoPair<_Ty1, _Ty2>;
public:
using Base::Base;
JOE((inline)) ~PairCat() noexcept = default;
JOE((constexpr)) PairCat(const PairCat&) noexcept = default;
JOE((constexpr)) PairCat(PairCat&&) noexcept = default;
template<typename Type>
JOE((constexpr)) Type& get() noexcept {
static_assert(is_any_of<Type, _Ty1, _Ty2>::value,
"The given type for get must be one of the compression types.");
return static_cast<Base*>(this)->get_type<Type>();
}
template<typename Type>
JOE((constexpr)) const Type& get() const noexcept {
static_assert(is_any_of<Type, _Ty1, _Ty2>::value,
"The given type for get must be one of the compression types.");
return static_cast<const Base*>(this)->get_type<Type>();
}
private:
template<typename...>
friend class PairCat;
};
看起来我们又添加了一个类型来包装 AutoPair
,但没有新的功能。但等等,还有更多!我们的下一个特化将消耗三种类型并创建一个类型和对的配对。在我们的 get
调用中,我们必须以不同的方式将调用分派到 get_type
。如果类型与第一个类型匹配,则可以直接使用到基类的转换中的 get_type
调用。如果它是其他两种类型之一,那么我们首先必须检索内部对或“PairBase
”,并使用该内部对调用 get_type
。这时 static if
的使用就派上用场了。
template<typename _Ty1, typename _Ty2, typename _Ty3>
class PairCat< _Ty1, _Ty2, _Ty3> : private AutoPair<_Ty1, AutoPair< _Ty2, _Ty3 > >
{
static_assert(!is_any_of<_Ty1, _Ty2, _Ty3>::value,
"The given types for compression must be unique.");
using PairBase = AutoPair< _Ty2, _Ty3 >;
using Base = AutoPair<_Ty1, PairBase >;
public:
template <typename _Other1, typename _Other2, typename _Other3>
JOE((constexpr)) explicit PairCat(_Other1&& _Arg1,
_Other2&& _Arg2, _Other3&& _Arg3)
noexcept(std::is_nothrow_constructible<Base>::value)
: Base(std::forward<_Other1>(_Arg1)
, std::forward< PairBase >(
PairBase(std::forward<_Other2>(_Arg2),
std::forward<_Other3>(_Arg3))
)
) { }
JOE((constexpr)) PairCat() noexcept = default;
JOE((inline)) ~PairCat() noexcept = default;
JOE((constexpr)) PairCat(const PairCat&) noexcept = default;
JOE((constexpr)) PairCat(PairCat&&) noexcept = default;
template<typename Type>
JOE((constexpr)) Type& get() noexcept {
static_assert(is_any_of<Type, _Ty1, _Ty2, _Ty3>::value,
"The given type for get must be one of the compression types.");
if constexpr (std::is_same<Type, _Ty1>::value)
return static_cast<Base*>(this)->template get_type<Type>();
else
return static_cast<Base*>(this)->template
get_type<PairBase>().template get_type<Type>();
}
template<typename Type>
JOE((constexpr)) const Type& get() const noexcept {
static_assert(is_any_of<Type, _Ty1, _Ty2, _Ty3>::value,
"The given type for get must be one of the compression types.");
if constexpr (std::is_same<Type, _Ty1>::value)
return static_cast<const Base*>(this)->template get_type<Type>();
else
return static_cast<const Base*>(this)->template
get_type<PairBase>().template get_type<Type>();
}
private:
template<typename...>
friend class PairCat;
};
下一个特化将是一个递归模板,一次弹出两个类型。这将处理所有其他大于三种类型的数量。它将继承自 type
和 AutoPair
的 type
和 PairCat
的 AutoPair
。PairCat
用于最内层的对,这会无限扩展递归。只有当类型数量为四个或更多时,才会命中此模板。它将弹出其中两个,然后继续到下一个 PairCat
。我们的 get
调用将如下分派:如果类型与类型一相同,则调用 get_type
并传入该 type
。如果 type
与 type
二相同,则先检索内部对,然后从中调用 get_type
。所有其他类型将通过首先检索内部对,然后检索最内层的对,然后从中调用 get
来处理,以使用该 PairCat
提供的分派。
template<typename _Ty1, typename _Ty2, typename ..._Types>
class PairCat< _Ty1, _Ty2, _Types...> : private AutoPair<_Ty1,
AutoPair< _Ty2, PairCat<_Types...> > >
{
static_assert(!is_any_of<_Ty1, _Ty2, _Types...>::value,
"The given types for compression must be unique.");
using CatBase = PairCat<_Types...>;
using PairBase = AutoPair< _Ty2, CatBase >;
using Base = AutoPair<_Ty1, PairBase >;
public:
template <typename _Other1, typename _Other2, typename ..._Others>
JOE((constexpr)) explicit PairCat(_Other1&& _Arg1,
_Other2&& _Arg2, _Others&& ..._Args)
noexcept(std::is_nothrow_constructible<Base>::value)
: Base(std::forward<_Other1>(_Arg1)
, std::forward< PairBase >(
PairBase(std::forward<_Other2>(_Arg2),
std::forward<PairCat<_Others...>
>(PairCat<_Others...>(std::forward<_Others>(_Args)...))
)
)
) { }
JOE((constexpr)) PairCat() noexcept = default;
JOE((inline)) ~PairCat() noexcept = default;
JOE((constexpr)) PairCat(const PairCat&) noexcept = default;
JOE((constexpr)) PairCat(PairCat&&) noexcept = default;
template<typename Type>
JOE((constexpr)) Type& get() noexcept {
static_assert(is_any_of<Type, _Ty1, _Ty2, _Types...>::value,
"The given type for get must be one of the compression types.");
if constexpr (std::is_same<Type, _Ty1>::value)
return static_cast<Base*>(this)->template get_type<Type>();
else if constexpr (std::is_same<Type, _Ty2>::value)
return static_cast<Base*>(this)->template
get_type<PairBase>().template get_type<Type>();
else
return static_cast<Base*>(this)->template
get_type<PairBase>().template get_type<CatBase>().template get<Type>();
}
template<typename Type>
JOE((constexpr)) const Type& get() const noexcept {
static_assert(is_any_of<Type, _Ty1, _Ty2, _Types...>::value,
"The given type for get must be one of the compression types.");
if constexpr (std::is_same<Type, _Ty1>::value)
return static_cast<const Base*>(this)->template get_type<Type>();
else if constexpr (std::is_same<Type, _Ty2>::value)
return static_cast<Base*>(this)->template
get_type<PairBase>().template get_type<Type>();
else
return static_cast<const Base*>(this)->template
get_type<PairBase>().template get_type<CatBase>().template get<Type>();
}
private:
template<typename...>
friend class PairCat;
};
现在剩下要做的就是通过 public using
语句/typedef
将我们的 PairCat
公开为一个完成的类型。
public:
using type = PairCat<Types...>;
};
// uses empty base class optimization compression
template<typename... Types>
using compressed_t = typename compressed<Types...>::type;
请注意,PairCat
以及所有其他辅助类型和特性都是 compressed 的 private type
s。只有 finished type
始终是 public
的。通过添加一个帮助类型 compressed_t
来提取 using
语句,我们的 compressed type
就完成了。
Using the Code
compressed_t
类型有很多用例。例如,作为类的成员,或作为一个单独的变量,或通过直接继承。带有单个类型的 compressed_t
将继承自 EbcoBase。如果给定的类型合适,这将允许动态地进行空基类优化,否则将其作为成员。作为类的成员,它可以用于将多个对象压缩到任何给定类型集合所需的最小空间。作为一个变量,嗯,你只是在吝啬,但可以做到。让我们看一下 std
代码片段的现状。
using MyAlloc = Rebind_alloc_t<Alloc, Ref_count_resource_alloc>;
virtual void _Destroy() noexcept override {
_Mypair._Get_first()(_Mypair._Myval2._Myval2); // deleter in first,
// resource in second's second
}
virtual void _Delete_this() noexcept override {
MyAlloc Al = _Mypair._Myval2._Get_first(); // allocator in second's first
this->~_Ref_count_resource_alloc();
std::_Deallocate_plain(Al, this);
}
std::_Compressed_pair<Dx, std::_Compressed_pair<Myalty,
Resource>> _Mypair; // pair of type and pair
这只有三种类型。随着每增加一个类型,语法会变得越来越困难。五种或更多类型,你就只是一团糟。现在让我们看一下替代语法。
using MyAlloc = Rebind_alloc_t<Alloc, Ref_count_resource_alloc>;
virtual void _Destroy() noexcept override {
comp.get<Dx>()(comp.get<Resource>()); // deleter in get,
// resource in get
}
virtual void _Delete_this() noexcept override {
MyAlloc Al = comp.get<MyAlloc>(); // allocator in get
this->~_Ref_count_resource_alloc();
std::_Deallocate_plain(Al, this);
}
joe::compressed_t<Dx, MyAlloc, Resource> comp; // type list
通常,使用压缩对时,您必须考虑哪一对应该进入哪个成员变量。因为压缩对的第二个总是成员变量。有了 compressed_t
,ebco 是动态的,尽管在底层,我们仍在制作类型和对的配对。但是,对压缩一个空类型与一个非空类型的更深层次的理解,将一个空类型放在前面,然后所有其他类型作为空类型和类型的成员变量……这可以通过一个简单的经验法则来避免。所有空类型都放在列表的前面(顺序任意),而所有非空类型都放在列表的最后(顺序任意)。因此……
compressed_t<Dx, MyAlloc, MyExtra, int*, double*> comp;
与……的大小和访问方式相同。
compressed_t<MyAlloc, MyExtra, Dx, double*, int*> comp;
现在让我们看看构造上的区别。
_Ref_count_resource_alloc(Resource Px, Dx Dt, const Alloc& Ax)
: _Ref_count_base(),
_Mypair(std::_One_then_variadic_args_t{}, std::move(Dt),
std::_One_then_variadic_args_t{}, Ax, Px) {}
_Ref_count_resource_alloc(Resource Px, Dx Dt, const Alloc& Ax)
: _Ref_count_base(),
comp(std::move(Dt), Ax, Px) {}
构造起来要简单得多。在构造 compressed_t
期间,没有任何辅助类型的构造函数会运行。相反,所有给定类型的构造函数都会就地运行,并且只有它们的值会被传递给 compressed_t
。但是,如果使用了未修饰的前向转发,您将在调试版本中看到完美转发的结果。使用已修饰的前向转发将改变这一点。否则,在发布版本或 O2 或更高级别的任何版本中,它都会被完全优化掉。我提到这一点只是因为一些公司出于某种原因只发布调试版本。辅助类型的析构函数也是如此。只有给定类型的析构函数才会被调用。get
调用的开销与使用就地引用变量的开销完全相同。也就是说,没有函数调用开销。由于它们被视为引用,并且如果它们的值可以在编译时证明,那么它们也有可能被优化掉。这使得它在运行时使用以及在大小方面都成为一种高效的解决方案。
为了证明我们的说法……
#pragma pack(push, 1)
#include <type_traits>
#include <iostream>
#include <stdexcept>
#include <memory>
#include "compressed.h"
using MyAlloc = std::allocator<int>;
// this type was added not for need, but to show off both the extent of compression,
// and simplicity of syntax
using MyCAlloc = std::allocator<char>;
using Dx = std::default_delete<int>;
// this type was added for the same reason as MyCAlloc
using DxC = std::default_delete<char>;
using Resource = int*;
int main()
{
// 5-types, the "mess threshold"
joe::compressed_t<Dx, DxC, MyAlloc, MyCAlloc,
Resource> comp(Dx{}, DxC{}, MyAlloc{}, MyCAlloc{}, new int(10));
//joe::compressed_t<Dx, DxC, MyAlloc, MyCAlloc, Resource> comp;
std::cout << "sizeof of compressed_t :
" << sizeof(decltype(comp)) << std::endl;
// size : should always be confirmed by a runtime test,
// after all alignments and packing have been applied
if (comp.get<Resource>() != nullptr) {
// sanity check is unneeded, but does prove the values
// are being passed along the constructor chains,
// this was a problem when trying to support default construction.
// Tweaking the enable if and adding copy and move to the helper types
// corrected this and now allows for both value construction,
// and default construction, provided the types themselves do.
std::cout << "valueof of compressed_t Resource : "
<< *comp.get<Resource>() << std::endl;
comp.get<Dx>()(comp.get<Resource>());
comp.get<Resource>() = comp.get<MyAlloc>().allocate(1);
(*comp.get<Resource>()) = 11;
std::cout << "valueof of compressed_t Resource : "
<< *comp.get<Resource>() << std::endl;
comp.get<Dx>()(comp.get<Resource>());
// these are some of the common things a custom smart pointer might do,
// we didn't wrap them in a type, so that we don't get lost in optimizations
// that would normally be performed on the custom type
// vs optimizations on the compressed_t.
}
else {
comp.get<Resource>() = comp.get<MyAlloc>().allocate(1);
(*comp.get<Resource>()) = 11;
std::cout << "valueof of compressed_t Resource : "
<< *comp.get<Resource>() << std::endl;
comp.get<Dx>()(comp.get<Resource>());
}
std::cin.ignore();
return 0;
}
#pragma pack(pop)
#undef JOE
这里是 Compiler Explorer 的链接,可以用来玩代码示例
有关在 CE 上使用 MSVC 编译的详细信息,请参阅狂野西部文章 MACROs。如果您直接在 MSVC 上使用它,则无需进行任何更改。否则,我们的目标平台是 Windows、Linux 和 Mac。目标编译器是 GCC、Clang 和 MSVC。
关注点
本文是为了回应 ISO 标准委员会正在研究使用 tuple 作为压缩对替代品的传言而写的。这提供了一种替代方案。由于它使用了鲜为人知的 std::get<Type>()
语法,因此与使用 tuple 差别不大。然而,tuple 不需要使用 ebco 压缩。这提供了两全其美。类型列表、get 语法和 ebco 压缩。但认为普通 C++ 用户,“普通乔”,一个人无法影响委员会,这是一种轻描淡写。这就是您作为读者和 C++ 社区成员可以带来变革的地方。您认为它如何堆叠。所有评论,无论好坏,都欢迎。我只请求如果您有设计上的不满,请发布一个可能的替代方案。这样,我们可以共同发展解决方案。
历史
- 2021 年 8 月 28 日:初始版本