优化 ODR-used empty 类
optimization for ODR-used empty classes
当今的许多 C++ 代码都倾向于最大程度地进行模板加载。它们是库:STL、Boost.Spirit、Boost.MPL 等。他们鼓励用户在表单中声明功能对象
struct S { /* presence of non-virtual member functions and operators, but absense of non-static data members or non-empty base classes */ }; S const s{};
。他们中的大多数是无国籍的(即 static_assert(std::is_empty< S >{});
成立)。对于它们中使用 ODR 的那些,不管它们的空性如何 data
文件增长 1 字节的部分(sizeof(S) == 1
对于空类型 S
因为所有随后分配的对象的地址都应该是不同的)。即使在 Boost.Spirit 的简单语法中,也有大量这样的 ODR-used 空 类。但是为他们保留space是绝对没有意义的。
我尝试使用以下代码 (-Ofast
) 在 coliru 上测试 clang
:
#include <utility>
#include <type_traits>
#include <cstdlib>
#include <cassert>
template< std::size_t index >
struct S {};
namespace
{
template< std::size_t index >
S< index > value = {};
}
template< typename lhs, typename rhs >
std::ptrdiff_t
diff(lhs & l, rhs & r)
{
return (static_cast< char * >(static_cast< void * >(&r)) - static_cast< char * >(static_cast< void * >(&l)));
}
template< std::size_t base, std::size_t ...indices >
std::ptrdiff_t
bss_check(std::index_sequence< indices... >)
{
return (diff(value< (base + indices) >, value< (base + indices + 1) >) + ...);
}
template< std::size_t size, std::size_t base >
bool
enumerate()
{
return (bss_check< base >(std::make_index_sequence< size >{}) + 1 == size);
}
template< std::size_t size, std::size_t ...bases >
bool
expand(std::index_sequence< bases... >)
{
return (enumerate< size, (bases * size) >() && ...);
}
template< std::size_t size = 100, std::size_t count = size >
bool
check()
{
return expand< size >(std::make_index_sequence< count >{});
}
int
main()
{
static_assert(std::is_empty< S< 0 > >{});
assert((check< DIM >()));
return EXIT_SUCCESS;
}
并得到结果(DIM == 100
的 size
实用程序的输出,即 100 * 100 类:
text data bss dec hex filename
112724 10612 4 123340 1e1cc ./a.out
如果我将 diff(lhs & l, rhs & r)
的签名更改为 diff(lhs l, rhs r)
以禁止使用 ODR,则结果为:
text data bss dec hex filename
69140 608 8 69756 1107c ./a.out
几乎等于(data
部分仅感兴趣)assert((check< DIM >()));
行的简单注释的情况(text
部分的主要部分是可预测的 DCE 优化掉) :
text data bss dec hex filename
1451 600 8 2059 80b ./a.out
因此我得出结论,没有针对 ODR-used empty 类 的优化。
对于明确指定的模板参数,可以使用简单的类型过滤器:
template< typename type >
using ref_or_value = std::conditional_t< std::is_empty< std::decay_t< type > >{}, std::decay_t< type >, type && >;
但在我看来,对于推导的模板类型没有简单的解决方法。
在现代编译器中是否隐含了上述优化?如果是,如何启用它?如果否,目前是否有实现所需行为的技术?
我知道有时对象的地址会很乱,但在上述情况下并非如此。
我认为诸如变量或类型的属性(例如 [[immaterial]]
)之类的东西会很方便。也许这样的属性(用于 类)应该拒绝获取属性实例的地址的可能性 类(编译时硬错误)或 address-of 运算符 &
应该 return 无意义值(实现定义)。
C++17 将添加内联变量来帮助解决 N4424 中解释的其中一些问题。它还解释了一些解决方法。对于全局函数对象,您可以这样定义它们:
// Sum function object
struct sum_f
{
template<class T, class U>
auto operator()(T x, U y) const
{
return x+y;
}
};
template<class T>
struct static_const_storage
{
static constexpr T value = T();
};
template<class T>
constexpr T static_const_storage<T>::value;
template<class T>
constexpr const T& static_const()
{
return static_const_storage<T>::value;
}
static constexpr auto& sum = static_const<sum_f>();
这使得 sum
函数对象在翻译单元中是唯一的,从而避免膨胀和 ODR 违规。但是,此变通方法并不真正适用于模板变量,最好避免使用它们(如果您担心可执行文件膨胀),直到我们在 C++17 中获得内联变量。
当今的许多 C++ 代码都倾向于最大程度地进行模板加载。它们是库:STL、Boost.Spirit、Boost.MPL 等。他们鼓励用户在表单中声明功能对象
struct S { /* presence of non-virtual member functions and operators, but absense of non-static data members or non-empty base classes */ }; S const s{};
。他们中的大多数是无国籍的(即 static_assert(std::is_empty< S >{});
成立)。对于它们中使用 ODR 的那些,不管它们的空性如何 data
文件增长 1 字节的部分(sizeof(S) == 1
对于空类型 S
因为所有随后分配的对象的地址都应该是不同的)。即使在 Boost.Spirit 的简单语法中,也有大量这样的 ODR-used 空 类。但是为他们保留space是绝对没有意义的。
我尝试使用以下代码 (-Ofast
) 在 coliru 上测试 clang
:
#include <utility>
#include <type_traits>
#include <cstdlib>
#include <cassert>
template< std::size_t index >
struct S {};
namespace
{
template< std::size_t index >
S< index > value = {};
}
template< typename lhs, typename rhs >
std::ptrdiff_t
diff(lhs & l, rhs & r)
{
return (static_cast< char * >(static_cast< void * >(&r)) - static_cast< char * >(static_cast< void * >(&l)));
}
template< std::size_t base, std::size_t ...indices >
std::ptrdiff_t
bss_check(std::index_sequence< indices... >)
{
return (diff(value< (base + indices) >, value< (base + indices + 1) >) + ...);
}
template< std::size_t size, std::size_t base >
bool
enumerate()
{
return (bss_check< base >(std::make_index_sequence< size >{}) + 1 == size);
}
template< std::size_t size, std::size_t ...bases >
bool
expand(std::index_sequence< bases... >)
{
return (enumerate< size, (bases * size) >() && ...);
}
template< std::size_t size = 100, std::size_t count = size >
bool
check()
{
return expand< size >(std::make_index_sequence< count >{});
}
int
main()
{
static_assert(std::is_empty< S< 0 > >{});
assert((check< DIM >()));
return EXIT_SUCCESS;
}
并得到结果(DIM == 100
的 size
实用程序的输出,即 100 * 100 类:
text data bss dec hex filename
112724 10612 4 123340 1e1cc ./a.out
如果我将 diff(lhs & l, rhs & r)
的签名更改为 diff(lhs l, rhs r)
以禁止使用 ODR,则结果为:
text data bss dec hex filename
69140 608 8 69756 1107c ./a.out
几乎等于(data
部分仅感兴趣)assert((check< DIM >()));
行的简单注释的情况(text
部分的主要部分是可预测的 DCE 优化掉) :
text data bss dec hex filename
1451 600 8 2059 80b ./a.out
因此我得出结论,没有针对 ODR-used empty 类 的优化。
对于明确指定的模板参数,可以使用简单的类型过滤器:
template< typename type >
using ref_or_value = std::conditional_t< std::is_empty< std::decay_t< type > >{}, std::decay_t< type >, type && >;
但在我看来,对于推导的模板类型没有简单的解决方法。
在现代编译器中是否隐含了上述优化?如果是,如何启用它?如果否,目前是否有实现所需行为的技术?
我知道有时对象的地址会很乱,但在上述情况下并非如此。
我认为诸如变量或类型的属性(例如 [[immaterial]]
)之类的东西会很方便。也许这样的属性(用于 类)应该拒绝获取属性实例的地址的可能性 类(编译时硬错误)或 address-of 运算符 &
应该 return 无意义值(实现定义)。
C++17 将添加内联变量来帮助解决 N4424 中解释的其中一些问题。它还解释了一些解决方法。对于全局函数对象,您可以这样定义它们:
// Sum function object
struct sum_f
{
template<class T, class U>
auto operator()(T x, U y) const
{
return x+y;
}
};
template<class T>
struct static_const_storage
{
static constexpr T value = T();
};
template<class T>
constexpr T static_const_storage<T>::value;
template<class T>
constexpr const T& static_const()
{
return static_const_storage<T>::value;
}
static constexpr auto& sum = static_const<sum_f>();
这使得 sum
函数对象在翻译单元中是唯一的,从而避免膨胀和 ODR 违规。但是,此变通方法并不真正适用于模板变量,最好避免使用它们(如果您担心可执行文件膨胀),直到我们在 C++17 中获得内联变量。