c++ - ODR 使用的空类的优化

标签 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 使用的空类。但是为他们保留空间是绝对没有意义的。

我尝试测试 clangcoliru使用以下代码 ( -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 实用程序的输出 DIM == 100,即 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 使用的空类进行优化。

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

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 operator & 的可能性。应该返回无意义的值(实现定义)。

最佳答案

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++ - ODR 使用的空类的优化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32787649/

相关文章:

c++ - VS2015 与 VS2013 中的不同优化导致浮点异常

c++ - C/C++ 转换为 const 怪异

c++ - Visual Studio 的 math.h 中有超过 200 个 ~SYNTAX ERRORS~,这没有意义

c++ - 检查字符串的两端均不起作用

c++ - 为什么在 std::shared_ptr 实现中需要两个指向托管对象的原始指针?

java - 将两个 ArrayList 添加到一个 hashmaps 的 ArrayList

c++ - 如何使用GetNumaProximityNode (Win7+)?

c++ - 读取然后处理流数据

c++ - Floyd-Warshall 算法的路径重建不适用于某些值

c++ - 将指向 lambda 函数的指针分配给指向另一个 lambda 函数的指针