2

今日の 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、空のタイプの場合)。SBoost.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;
}

結果を取得します ( sizeutility forの出力DIM == 100、つまり 100 * 100 クラス):

   text    data     bss     dec     hex filename
 112724   10612       4  123340   1e1cc ./a.out

ODR の使用を抑制するために署名を変更diff(lhs & l, rhs & r)すると、結果は次のようになります。diff(lhs l, rhs r)

  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 で使用される空のクラスの最適化はないと結論付けます。

明示的に指定されたテンプレート パラメーターには、単純な typefilter を使用する可能性があります。

template< typename type >
using ref_or_value = std::conditional_t< std::is_empty< std::decay_t< type > >{}, std::decay_t< type >, type && >;

しかし、推測されたテンプレートの種類に対する簡単な回避策は思い浮かびません。

最新のコンパイラでは、上記の最適化が暗示されていますか? はいの場合、それを有効にする方法は?いいえの場合、現時点で望ましい動作を実現するための手法はありますか?

オブジェクトのアドレスがつぶやくことがあることは知っていますが、上記の状況ではそうではありません。

[[immaterial]]変数や型の属性のようなもの(例)が便利だと思います。おそらく、そのような属性 (クラスに使用される) は、属性付きクラスのインスタンスのアドレスを取得する可能性を否定する必要があります (コンパイル時のハードエラー) か、アドレスの演算子は&意味のない値を返す必要があります (実装定義)。

4

1 に答える 1

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 でインライン変数を取得するまでは (実行可能ファイルの肥大化が懸念される場合)、テンプレート変数を回避することをお勧めします。

于 2015-09-29T22:05:04.357 に答える