20

これは、C ++で可変サイズの構造体を作成するための最良の方法ですか?初期化後も長さが変わらないので、ベクトルは使いたくありません。

struct Packet
{
    unsigned int bytelength;
    unsigned int data[];
};

Packet* CreatePacket(unsigned int length)
{
    Packet *output = (Packet*) malloc((length+1)*sizeof(unsigned int));
    output->bytelength = length;
    return output;
}

編集:変数名の名前を変更し、コードをより正確に変更しました。

4

11 に答える 11

9

あなたがしていることについてのいくつかの考え:

  • C スタイルの可変長構造体イディオムを使用すると、パケットごとに 1 つの空きストア割り当てを実行できます。これはstruct Packetstd::vector. 非常に多くのパケットを割り当てている場合は、空きストアの割り当て/割り当て解除の半分の回数を実行することが非常に重要になる可能性があります。ネットワーク アクセスも行っている場合、ネットワークの待機に費やす時間はおそらくより重要になります。

  • この構造体はパケットを表します。ソケットから に直接読み書きする予定struct Packetですか? もしそうなら、おそらくバイトオーダーを考慮する必要があります。パケットを送信するときにホストからネットワークのバイト順に変換する必要がありますか? また、パケットを受信するときにその逆も必要ですか? もしそうなら、可変長構造体でデータをバイトスワップすることができます。これをベクターを使用するように変換した場合、パケットをシリアライズ/デシリアライズするためのメソッドを記述することは理にかなっています。これらのメソッドは、バイト順を考慮して、連続したバッファとの間でデータを転送します。

  • 同様に、アライメントとパッキングを考慮する必要がある場合があります。

  • をサブクラス化することはできませんPacket。その場合、サブクラスのメンバー変数が配列と重複します。

  • andの代わりにmallocandfreeを使用できます。これは、 が POD 型でPacket* p = ::operator new(size)あり、現在のところ、デフォルトのコンストラクターとデストラクタを呼び出すメリットがないためです。そうすることの(潜在的な)利点は、グローバルがグローバル new-handler および/または例外を使用してエラーを処理することです。::operator delete(p)struct Packetoperator new

  • 可変長の構造体イディオムを new および delete 演算子で機能させることは可能ですが、うまくいきません。operator newを実装することで配列の長さを取るカスタムを作成できますがstatic void* operator new(size_t size, unsigned int bitlength)、それでも bitlength メンバー変数を設定する必要があります。コンストラクターでこれを行うと、少し冗長な式を使用しPacket* p = new(len) Packet(len)てパケットを割り当てることができます。operator newグローバルを使用する場合と比較して私が見る唯一の利点operator deleteは、コードのクライアントが のdelete p代わりに呼び出すことができることです::operator delete(p)。割り当て/割り当て解除を (直接呼び出すのではなく) 別の関数でラップしてdelete pも、それらが正しく呼び出される限り問題ありません。

于 2009-03-27T05:15:01.753 に答える
7

コンストラクター/デストラクタ、代入演算子または仮想関数を構造体に追加しない場合は、割り当てに malloc/free を使用するのが安全です。

C++ サークルでは嫌われていますが、コードで文書化すれば使用しても問題ないと思います。

コードへのコメント:

struct Packet
{
    unsigned int bitlength;
    unsigned int data[];
};

長さのない配列を正しく宣言することは非標準です。ほとんどのコンパイラで機能しますが、警告が表示される場合があります。準拠したい場合は、長さ1の配列を宣言してください。

Packet* CreatePacket(unsigned int length)
{
    Packet *output = (Packet*) malloc((length+1)*sizeof(unsigned int));
    output->bitlength = length;
    return output;
}

これは機能しますが、構造のサイズを考慮していません。構造に新しいメンバーを追加すると、コードが壊れます。このようにする方が良い:

Packet* CreatePacket(unsigned int length)
{
    size_t s = sizeof (Packed) - sizeof (Packed.data);
    Packet *output = (Packet*) malloc(s + length * sizeof(unsigned int));
    output->bitlength = length;
    return output;
}

そして、データが最後のメンバーでなければならないというコメントをパケット構造定義に書き込みます。

ところで - 単一の割り当てで構造とデータを割り当てることは良いことです。そのようにして割り当ての数を半分にし、データの局所性も向上させます。多くのパッケージを割り当てる場合、これによりパフォーマンスが大幅に向上します。

残念ながら、c++ はこれを行うための優れたメカニズムを提供していないため、実際のアプリケーションではこのような malloc/free ハックに陥ることがよくあります。

于 2009-03-27T04:03:22.537 に答える
5

これで問題ありません (C の標準的な方法でした)。

しかし、これは C++ にとっては良い考えではありません。
これは、コンパイラがクラスの周りに他のメソッドのセット全体を自動的に生成するためです。これらのメソッドは、あなたがだまされたことを理解していません。

例えば:

void copyRHSToLeft(Packet& lhs,Packet& rhs)
{
    lhs = rhs;  // The compiler generated code for assignement kicks in here.
                // Are your objects going to cope correctly??
}


Packet*   a = CreatePacket(3);
Packet*   b = CreatePacket(5);
copyRHSToLeft(*a,*b);

std::vector<> を使用すると、はるかに安全で正しく動作します。
また、オプティマイザーが作動した後の実装と同じくらい効率的であるに違いありません。

あるいは、ブーストには固定サイズの配列が含まれています:
http://www.boost.org/doc/libs/1_38_0/doc/html/array.html

于 2009-03-27T04:38:39.347 に答える
3

必要に応じて「C」メソッドを使用できますが、安全のために、コンパイラがコピーしようとしないようにします。

struct Packet
{
    unsigned int bytelength;
    unsigned int data[];

private:
   // Will cause compiler error if you misuse this struct
   void Packet(const Packet&);
   void operator=(const Packet&);
};
于 2009-03-27T14:03:40.953 に答える
2

vector<>最小限の余分なオーバーヘッド (おそらく実装に対する単一の余分な単語またはポインター) が実際に問題を引き起こしている場合を除き、私はおそらく a の使用に固執するでしょう。ベクトルが構築されたら resize() しなければならないということは何もありません。

ただし、いくつかの利点がありますvector<>

  • すでにコピー、割り当て、および破棄を適切に処理しています。独自のロールを作成する場合は、これらを正しく処理する必要があります
  • イテレータのサポートはすべてそこにあります - 繰り返しますが、自分でロールする必要はありません。
  • 誰もがすでにそれを使用する方法を知っています

一度構築された配列が大きくなるのを本当に防ぎたい場合は、vector<>プライベートに継承するか、vector<>メンバーを持ち、クライアントに必要なベクトルのビットをベクターメソッドにサンクするメソッドを介してのみ公開する独自のクラスを持つことを検討することをお勧めします。使用できるようにします。これにより、漏れや漏れがないことを十分に保証して、迅速に作業を進めることができます。これを行って vector の小さなオーバーヘッドがうまくいかないことがわかった場合は、vector の助けを借りずにそのクラスを再実装でき、クライアント コードを変更する必要はありません。

于 2009-03-27T04:45:10.873 に答える
1

ここにはすでに多くの良い考えがあります。しかし、1つ欠けています。柔軟な配列は C99 の一部であるため、C++ の一部ではありませんが、一部の C++ コンパイラがこの機能を提供する場合がありますが、その保証はありません。許容できる方法で C++ でそれらを使用する方法を見つけたが、それをサポートしていないコンパイラがある場合は、おそらく「古典的な」方法にフォールバックできます。

于 2009-03-27T09:22:20.863 に答える
0

高性能を実現するには、ベクトルよりも軽いものが必要になるでしょう。また、クロスプラットフォームにするパケットのサイズについて非常に具体的にする必要があります。ただし、メモリリークについても気にする必要はありません。

幸い、Boostライブラリは難しい部分のほとんどを実行しました。

struct packet
{
   boost::uint32_t _size;
   boost::scoped_array<unsigned char> _data;

   packet() : _size(0) {}

       explicit packet(packet boost::uint32_t s) : _size(s), _data(new unsigned char [s]) {}

   explicit packet(const void * const d, boost::uint32_t s) : _size(s), _data(new unsigned char [s])
   {
        std::memcpy(_data, static_cast<const unsigned char * const>(d), _size);
   }
};

typedef boost::shared_ptr<packet> packet_ptr;

packet_ptr build_packet(const void const * data, boost::uint32_t s)
{

    return packet_ptr(new packet(data, s));
}
于 2009-03-27T10:44:18.220 に答える
0

初期化後に固定される未知のサイズの配列に vector を使用しても、まったく問題はありません。私見、それはまさにベクトルの目的です。初期化したら、配列であると見なすことができ、同じように動作するはずです (時間の動作を含む)。

于 2009-03-27T20:23:30.433 に答える
0

本当に C++ を使用している場合、デフォルトのメンバーの可視性を除いて、クラスと構造体の間に実質的な違いはありません。クラスにはデフォルトでプライベートな可視性があり、構造体にはデフォルトでパブリックな可視性があります。以下は同等です。

struct PacketStruct
{
    unsigned int bitlength;
    unsigned int data[];
};
class PacketClass
{
public:
    unsigned int bitlength;
    unsigned int data[];
};

要点は、CreatePacket() は必要ないということです。コンストラクターを使用して、構造体オブジェクトを簡単に初期化できます。

struct Packet
{
    unsigned long bytelength;
    unsigned char data[];

    Packet(unsigned long length = 256)  // default constructor replaces CreatePacket()
      : bytelength(length),
        data(new unsigned char[length])
    {
    }

    ~Packet()  // destructor to avoid memory leak
    {
        delete [] data;
    }
};

注意すべき点がいくつかあります。C++ では、malloc の代わりに new を使用します。私は自由を取り、ビット長をバイト長に変更しました。このクラスがネットワーク パケットを表す場合、(私の意見では) ビットではなくバイトを処理する方がはるかに優れています。データ配列は、unsigned int ではなく、unsigned char の配列です。繰り返しますが、これは、このクラスがネットワーク パケットを表すという私の仮定に基づいています。コンストラクターを使用すると、次のようなパケットを作成できます。

Packet p;  // default packet with 256-byte data array
Packet p(1024);  // packet with 1024-byte data array

Packet インスタンスがスコープ外になると、デストラクタが自動的に呼び出され、メモリ リークが防止されます。

于 2009-03-27T04:02:55.907 に答える
0

長さが指定されていない配列ではなく、ポインターを宣言する必要があります。

于 2009-03-27T04:10:17.650 に答える