ODR使用的空类的优化

时间:2015-09-25 17:31:20

标签: c++ c++11 optimization compiler-optimization one-definition-rule

现在很多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使用的空类。但为他们留出空间绝对没有意义。

我尝试使用以下代码(clang)在coliru上测试-Ofast

#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;
}

并获得结果(size的{​​{1}}实用程序的输出,即100 * 100个类:

DIM == 100

如果我将 text data bss dec hex filename 112724 10612 4 123340 1e1cc ./a.out 的签名更改为diff(lhs & l, rhs & r)以禁止使用ODR,则结果为:

diff(lhs l, rhs r)

几乎等于( text data bss dec hex filename 69140 608 8 69756 1107c ./a.out 部分只是感兴趣)data行的简单评论(assert((check< DIM >()));部分的主要部分是可预测的DCE优化的):< / p>

text

因此我得出结论,ODR使用的空类没有优化。

对于明确指定的模板参数,可以使用简单的类型过滤器:

   text    data     bss     dec     hex filename
   1451     600       8    2059     80b ./a.out

但在我看来,推导出的模板类型没有简单的解决方法。

现代编译器中是否存在以上优化?如果是,如何启用它?如果不是,目前是否有一种技术可以达到预期的行为?

我知道有些对象的地址变得很多,但上述情况并非如此。

我认为类似变量或类型的属性(例如template< typename type > using ref_or_value = std::conditional_t< std::is_empty< std::decay_t< type > >{}, std::decay_t< type >, type && >; )会很方便。也许这样的属性(用于类)应该否定获取属性类的实例地址的可能性(编译时硬错误)或地址 - 运算符[[immaterial]] shoud返回非意义值(实现定义)。

1 个答案:

答案 0 :(得分:1)

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中获取内联变量。