web-dev-qa-db-ja.com

std :: arrayビットは古いC配列と互換性がありますか?

_std::array<T,N> v_と_T u[N]_の基礎となるビット表現は同じですか?

言い換えれば、N*sizeof(T)バイトを一方から他方へコピーしても安全ですか? (_reinterpret_cast_またはmemcpyのいずれかを使用します。)

編集:

明確にするために、強調は同じビット表現と_reinterpret_cast_に重点を置いています。

たとえば、いくつかのTについて、いくつかの自明なコピーが可能な型Nにこれらの2つのクラスがあるとします。

_struct VecNew {
    std::array<T,N> v;
};

struct VecOld {
    T v[N];
};
_

そしてレガシー機能があります

_T foo(const VecOld& x);
_

表現が同じ場合、この呼び出しは安全であり、コピーを回避します。

_VecNew x;
foo(reinterpret_cast<const VecOld&>(x));
_
33
shinjin

私は「はい」と言います(しかし、標準はそれを保証しません)。

[配列]/2によれば:

配列はaggregate([ dcl.init.aggr ])であり、Tに変換可能な型を持つ最大N個の要素でリスト初期化できます。

そして[dcl.init.aggr]:

aggregateは配列またはクラス(句[クラス])で、

  • ユーザー提供、明示的、または継承されたコンストラクター([class.ctor])はありません。

  • プライベートまたは保護された非静的データメンバー(条項[class.access])、

  • 仮想関数なし([class.virtual])、および

  • 仮想、プライベート、または保護された基本クラス([class.mi])はありません。

これに照らして、 "can can be list-initialized"は、クラスの先頭に他のメンバーがなく、vtableがない場合にのみ可能です。

次に、data()は次のように指定されます。

constexpr T* data() noexcept;

戻り値[data(), data() + size())が有効な範囲であるようなポインター、およびdata() == addressof(front())

標準は基本的に「配列を返す」と言いたいが、他の実装のための扉は開いたままにしている。

他に可能な唯一の実装は、個々の要素を持つ構造です。その場合、エイリアスの問題にできます。しかし、私の見解では、このアプローチは複雑さ以外に何も追加しません。配列を構造体に展開しても何も得られません。

したがって、意味がないではないstd::arrayを配列として。

しかし、抜け穴は存在します。

9
rustyx

これはあなたの質問に直接答えることはしませんが、単に std::copy

T c[N];
std::array<T, N> cpp;

// from C to C++
std::copy(std::begin(c), std::end(c), std::begin(cpp));

// from C++ to C
std::copy(std::begin(cpp), std::end(cpp), std::begin(c));

Tがささいにコピー可能なタイプの場合、これはmemcpyにコンパイルされます。そうでない場合、これは要素ごとのコピー割り当てを行い、正しくなります。いずれにせよ、これは正しいことを行い、非常に読みやすいです。手動のバイト演算は必要ありません。

20
Barry

std::arrayが提供するメソッド data() これは、適切なサイズのcスタイルの配列との間でコピーするために使用できます。

const size_t size = 123;
int carray[size];
std::array<int,size> array;

memcpy( carray, array.data(), sizeof(int) * size );
memcpy( array.data(), carray, sizeof(int) * size );

ドキュメント で述べたように

このコンテナーは、Cスタイルの配列T [N]を唯一の非静的データメンバーとして保持する構造体と同じセマンティクスを持つ集計型です。

したがって、メモリフットプリントはCスタイルの配列と互換性があるようですが、オーバーヘッドのない適切な方法がある場合にreinterpret_castで「ハック」を使用する理由は明確ではありません。

13
Slava

data()メソッドの要件は、次のようなポインタ_T*_を返すことです。

[data(), data() + size())は有効な範囲であり、data() == addressof(front())です。

これは、data()ポインターを介して各要素に順次アクセスできることを意味します。したがって、Tが簡単にコピーできる場合は、memcpyを使用してsizeof(T) * size()バイトをコピーできます。これは、配列T[size()]との間で行われます。これは、各要素を個別にmemcpyingすることと同じであるためです。

ただし、_reinterpret_cast_を使用することはできません。これは、厳密なエイリアスに違反するためです。data()実際にはを配列で使用する必要がないためです。 C++ 17では(_std::array_を使用しても)配列へのポインターをその最初のメンバーへ/からポインターからキャストできないため、_reinterpret_cast_に配列が含まれていることを保証します(_std::launder_)。

9
ecatmur

arrayは、それをインスタンス化する基礎となる型についてはあまり要求しません。

anyを使用して、memcpyまたはreinterpret_castコピーを行うには、それをインスタンス化した型を簡単にコピーできるようにする必要があります。これらのアイテムをarrayに保存しても、memcpy(など)が単純にコピー可能な型でのみ機能するという要件には影響しません。

arrayは、連続するコンテナーおよび集約である必要があります。これは、要素のストレージが配列でなければならないことを意味します。標準はそれを次のように示しています:

T elems[N]; // exposition only

ただし、後で、少なくとも配列が必要であることを示唆している(§[array.overview]/4)という注記があります。

[注:メンバー変数elemsは、arrayがクラスの集合体であることを強調するために、説明のためにのみ表示されています。 名前elemsはアレイのインターフェイスの一部ではありません。 —注の終了]

【強調追加】

実際には、必須ではない特定の名前elemsだけであることに注意してください。

7
Jerry Coffin