23
4

6 に答える 6

11

メンバー関数と を使用して、多かれ少なかれ同じ効果を得ることができますreinterpret_cast

int* buffer() { return reinterpret_cast<int*>(this + 1); }

これには大きな欠点が 1 つあります。正しい配置が保証されないということです。たとえば、次のようなものです。

struct Hack
{
    char size;
    int* buffer() { return reinterpret_cast<int*>(this + 1); }
};

整列されていないポインターを返す可能性があります。これを回避するには、構造体のデータを、返すポインターの型との共用体に入れます。C++11 を使用している場合は、次のように宣言できます。

struct alignas(alignof(int)) Hack
{
    char size;
    int* buffer() { return reinterpret_cast<int*>(this + 1); }
};

(と思います。実際にこれを試したことはありません。構文の詳細が間違っている可能性があります。)

このイディオムには 2 番目の重大な欠陥があります。サイズ フィールドが実際のバッファー サイズに対応することを保証するものではなく、さらに悪いことに、newここで実際に使用する方法がありません。これを修正するために、クラス固有の operator newandを定義できoperator deleteます。

struct alignas(alignof(int)) Hack
{
    void* operator new( size_t, size_t n );
    void operator delete( void* );
    Hack( size_t n );
    char size;
    int* buffer() { return reinterpret_cast<int*>(this + 1); }
};

次に、クライアント コードは、placement new を使用して割り当てる必要があります。

Hack* hack = new (20) Hack(20);

クライアントはサイズを繰り返す必要がありますが、無視することはできません。

動的に割り当てられていないインスタンスの作成を防ぐために使用できる手法もあります。最終的には次のようになります。

struct alignas(alignof(int)) Hack
{
private:
    void operator delete( void* p )
    {
        ::operator delete( p );
    }
    //  ban all but dynamic lifetime (and also inheritance, member, etc.)
    ~Hack() = default;

    //  ban arrays
    void* operator new[]( size_t ) = delete;
    void operator delete[]( void* p ) = delete;
public:
    Hack( size_t n );
    void* operator new( size_t, size_t n )
    {
        return ::operator new( sizeof(Hack) + n * sizeof(int) );
    }
    char size;
    //  Since dtor is private, we need this.
    void deleteMe() { delete this; }
    int* buffer() { return reinterpret_cast<int*>(this + 1); }
};

そのようなクラスの基本的な危険性を考えると、非常に多くの保護対策が必要かどうかは議論の余地があります. それらがあっても、すべての制約を完全に理解し、慎重に注意を払っている人だけが実際に使用できます。極端な場合を除いて、非常に低レベルのコードでは、バッファを astd::vector<int>にすれば完了です。最低レベルのコードを除いて、パフォーマンスの違いはリスクと労力に見合うものではありません。

編集:

例として、 g++ の の実装で は、参照カウント、現在のサイズ、および現在の容量 (3 ) が含まれ、その後に文字バッファーが直接続くstd::basic_string、上記と非常によく似たものを使用します。また、これは C++11 や/よりも ずっと前に作成されたため、一部のシステム (Sparc など) ではクラッシュする可能性があります。(技術的にはバグですが、ほとんどの人はこれを重大な問題とは考えていません。)structsize_talignasalignofstd::basic_string<double>

于 2013-11-29T17:22:22.470 に答える
8

最初に頭に浮かぶのは、C++ で C を記述しないでください99.99% のケースでこれhackは必要なく、保持するよりもパフォーマンスが目立って向上することはなく、std::vectorあなたの生活やこれをデプロイするプロジェクトの他のメンテナーの生活を複雑にします。

hack標準に準拠したアプローチが必要な場合は、 (配列を差し引いた) プラス配列に相当するものを含むのに十分な大きさのメモリのチャンクを動的に割り当てるラッパー型を提供します(N*sizeof(int)適切なアライメントを確保することを忘れないでください)。クラスには、メンバーと配列要素をメモリ内の正しい場所にマップするアクセサーがあります。

配置とボイラー プレート コードを無視して、インターフェイスを適切に、実装を安全にします。

template <typename T>
class DataWithDynamicArray {
   void *ptr;
   int* array() {
      return static_cast<int*>(static_cast<char*>(ptr)+sizeof(T)); // align!
   }
public:
   DataWithDynamicArray(int size) : ptr() {
      ptr = malloc(sizeof(T) + sizeof(int)*size); // force correct alignment
      new (ptr) T();
   }
   ~DataWithDynamicArray() { 
      static_cast<T*>(ptr)->~T();
      free(ptr);
   }
// copy, assignment...
   int& operator[](int pos) {
       return array()[pos];
   }
   T& data() {
      return *static_cast<T*>(ptr);
    }
};

struct JustSize { int size; };
DataWithDynamicArray<JustSize> x(10);
x.data().size = 10
for (int i = 0; i < 10; ++i) {
    x[i] = i;
}

たとえば、サイズはDataWithDynamicArray...

この回答は、拡張機能なしで同じことができることを説明するための演習としてのみ提供されていますが、これはおもちゃの例にすぎず、例外の安全性やアライメントを含むがこれらに限定されない多くの問題があることに注意してください (それでも、ユーザーmallocは正しいサイズで行う必要があります)。できるという事実は、そうするべきだという意味ではなく、本当の問題は、この機能が必要かどうか、そしてあなたがやろうとしていることは良いデザインであるかどうかです.

于 2013-11-29T17:41:56.713 に答える
4

本当にハックを使用する必要があると感じている場合は、使用しないでください

struct hack {
  char filler;
  int things[1];
};

に続く

hack_p = malloc(sizeof(struct hack)+(N-1)*sizeof int));

または、-1 を気にせず、少し余分なスペースを確保してください。

于 2013-11-29T20:28:03.703 に答える
3

C++ には「柔軟な配列」という概念がありません。C++ で柔軟な配列を持つ唯一の方法は、動的配列を使用することint* thingsです。適切なサイズの配列を作成できるように、ファイルからこのデータを読み取ろうとしている場合は、サイズ パラメータが必要です (または、を使用してstd::vector、ストリームの最後に到達するまで読み取りを続けます)。

「柔軟な配列」ハックは、動的メモリの使用を余儀なくされたときに失われる空間的局所性 (つまり、構造の残りの部分に連続したブロックにメモリが割り当てられている) を保持します。それを回避するエレガントな方法はありません (たとえば、大きなバッファーを割り当てることはできますが、必要な要素をいくつでも保持できる十分な大きさにする必要があります。実際に読み取られるデータが、バッファーに無駄なスペースが割り当てられます)。

また、代わりに構造体に個別に割り当てられたメモリの一部に int* を保持することを人々が提案し始める前に、それは満足のいく答えではありません。単一のメモリを割り当てて、構造体と配列要素の両方を保持したいと考えています。std::vector の使用も同じカテゴリに分類されます。

これ、C++ で行う方法です。好きなだけ反対票を投じることができますが、事実は残ります。非標準の拡張機能は、それをサポートしていないコンパイラに移行すると機能しなくなります。標準に従えば (たとえば、コンパイラ固有のハックを使用しないようにするなど)、この種の問題に遭遇する可能性は低くなります。

于 2013-11-29T16:52:15.830 に答える