私は、クラスのメモリレイアウトをテンプレート化されたコードでより効果的にするための可能な方法について疑問を抱いていました。私が知る限り、Standardはクラスのデータメンバーが宣言の順番でメモリに配置されるように指示します。クラスのサイズに不必要に追加するデータメンバーを整列させるために、コンパイラーによってパディングが行われる可能性があります。このようなパディングを最小限に抑えるために、データメンバ宣言をコンパイル時に再配置することが考えられます。私はいくつかの検索をしましたが、情報を見つけることができませんでした(ほとんどの場合、人々はコンパイラー・ディレクティブを議論します。データメンバーのコンパイル時の再配置?
まず、以下の(些細な、しかし、反復的で醜い)コード(same code on ideone.com)(質問は、コードの下にある、右それらまでスキップして自由に感じる)を検討してください:
#include <iostream>
#include <cstdint>
namespace so
{
template <typename Ta, typename Tb, typename Tc, std::size_t =
((sizeof(Ta) >= sizeof(Tb)) && (sizeof(Tb) >= sizeof(Tc))) ? 10 :
((sizeof(Ta) >= sizeof(Tc)) && (sizeof(Tc) >= sizeof(Tb))) ? 11 :
((sizeof(Tb) >= sizeof(Ta)) && (sizeof(Ta) >= sizeof(Tc))) ? 20 :
((sizeof(Tb) >= sizeof(Tc)) && (sizeof(Tc) >= sizeof(Ta))) ? 21 :
((sizeof(Tc) >= sizeof(Ta)) && (sizeof(Ta) >= sizeof(Tb))) ? 30 :
((sizeof(Tc) >= sizeof(Tb)) && (sizeof(Tb) >= sizeof(Ta))) ? 31 : 0>
struct foo {};
template <typename Ta, typename Tb, typename Tc>
struct foo<Ta, Tb, Tc, 10>
{
Ta a;
Tb b;
Tc c;
foo(Ta _a, Tb _b, Tc _c) : a{_a}, b{_b}, c{_c} {}
};
template <typename Ta, typename Tb, typename Tc>
struct foo<Ta, Tb, Tc, 11>
{
Ta a;
Tc c;
Tb b;
foo(Ta _a, Tb _b, Tc _c) : a{_a}, c{_c}, b{_b} {}
};
template <typename Ta, typename Tb, typename Tc>
struct foo<Ta, Tb, Tc, 20>
{
Tb b;
Ta a;
Tc c;
foo(Ta _a, Tb _b, Tc _c) : b{_b}, a{_a}, c{_c} {}
};
template <typename Ta, typename Tb, typename Tc>
struct foo<Ta, Tb, Tc, 21>
{
Tb b;
Tc c;
Ta a;
foo(Ta _a, Tb _b, Tc _c) : b{_b}, c{_c}, a{_a} {}
};
template <typename Ta, typename Tb, typename Tc>
struct foo<Ta, Tb, Tc, 30>
{
Tc c;
Ta a;
Tb b;
foo(Ta _a, Tb _b, Tc _c) : c{_c}, a{_a}, b{_b} {}
};
template <typename Ta, typename Tb, typename Tc>
struct foo<Ta, Tb, Tc, 31>
{
Tc c;
Tb b;
Ta a;
foo(Ta _a, Tb _b, Tc _c) : c{_c}, b{_b}, a{_a} {}
};
template <typename Ta, typename Tb, typename Tc>
struct bar: public foo<Ta, Tb, Tc>
{
private:
using base = foo<Ta, Tb, Tc>;
public:
bar() = default;
using base::base;
};
template <typename Ta, typename Tb, typename Tc>
struct foobar
{
Ta a;
Tb b;
Tc c;
foobar() = default;
foobar(Ta _a, Tb _b, Tc _c) : a{_a}, b{_b}, c{_c} {}
};
} //namespace so
int main()
{
so::bar<std::uint16_t, std::uint32_t, std::uint16_t> bar{1, 2, 3};
so::foobar<std::uint16_t, std::uint32_t, std::uint16_t> foobar{1, 2, 3};
std::cout << sizeof(bar) << "\t" << sizeof(foobar) << std::endl;
std::cout << bar.a << " : " << bar.b << " : " << bar.c << std::endl;
std::cout << foobar.a << " : " << foobar.b << " : " << foobar.c << std::endl;
return (0);
}
プログラムの出力:
を8 12
1 : 2 : 3
1 : 2 : 3
質問:
- Iそのようなことを解決するためのコンパイラに依存しない方法がいくつか知られています(Boost、多分)。
- もしそうでなければ、GCCの
__atribute__((packed))
のようなデータの不整列を伴わないで自動的にそのようなことを行うコンパイラ特有の指示があるでしょうか? - もっと汎用的な方法で(これは多分可変的なテンプレートを使って)行うことができますか?
ありがとうございます!
ヒント:継承を使用することで繰り返しの問題を解決できると思いますが、バリデーションテンプレートの扉を開きますが、これはコンパイラに大きく依存しますが、コードはあまりエレガントではないかもしれません(別名、 'get <0>(foo)'のように要素にアクセスする関数)。また、アラインメントではなく、サイズを調べているだけで、いくつかのアーキテクチャでは 'double'は8バイト長ですが、4バイト境界にアライメントされるので、計算が最適ではないかもしれません。 –
@MatthieuM。パラメータパックを展開し、 'get <>'なんらかの種類を使ってそれをナビゲートすると、再帰的な継承のようなものなのですか?アライメントは 'alignof()/ std :: alignment_of <>'で扱うことができます。私は想像しています - それを指摘してくれてありがとう。 – lapk
実際には、私の主な問題は残念ながら、属性のパック展開を使用することはできません(厄介な)、それに対処するさまざまな方法があります...そして、私は 'std :: tuple'継承トリックを使って再実装しようとするのではなく、単に 'std :: tuple'を再利用することができました:D –