7

標準 C では、サイズ 0 の配列で構造体を終了し、それを過剰に割り当てて、可変長の次元を配列に追加できます。

struct var
{
    int a;
    int b[];
}

struct var * x=malloc(sizeof(var+27*sizeof(int)));

C++ で標準的な (移植可能な) 方法でそれを行うにはどうすればよいでしょうか? 可能な最大サイズの制約があっても問題なく、明らかにスタックで動作する必要はありません

私は考えていました:

class var
{
...
private:
  int a;
  int b[MAX];
};

次に、アロケーターを使用するか、new/delete をオーバーロードして、必要なサイズに基づいてアンダーアロケーションします。

(sizeof(var) - (MAX-27)*sizeof(int)

しかし、それは機能しているように見えますが、私が維持しなければならないものではありません.

完全に標準/移植可能なよりクリーンな方法はありますか?

4

8 に答える 8

3

C の方法の変形を単純に行うことの何が問題なのですか?

構造を純粋に POD のままにする必要がある場合は、C の方法で問題ありません。

struct var
{
    int a;
    int b[1];

    static std::shared_ptr<var> make_var(int num_b) {
        const extra_bytes = (num_b ? num_b-1 : 0)*sizeof(int);
        return std::shared_ptr<var>(
                new char[sizeof(var)+extra_bytes ],
                [](var* p){delete[]((char*)(p));});
}

これは POD であるため、すべてが C で行ったのと同じように機能します。


が POD であることが保証されていない場合b、事態はさらに興味深いものになります。私はそれをテストしていませんが、多かれ少なかれそのように見えます。ラムダ デストラクタを使用するため、 にmake_var依存していることに注意してください。make_uniqueこれがなくても動作させることができますが、それはより多くのコードです。これは C の方法と同じように機能しますが、コンストラクタとデストラクタを使用して可変量の型をきれいに処理し、例外を処理します。

template<class T>
struct var {
    int a;

    T& get_b(int index) {return *ptr(index);}
    const T& get_b(int index) const {return *ptr(index);}

    static std::shared_ptr<var> make_var(int num_b);
private:
    T* ptr(int index) {return static_cast<T*>(static_cast<void*>(&b))+i;}
    var(int l);
    ~var();
    var(const var&) = delete;
    var& operator=(const var&) = delete;

    typedef typename std::aligned_storage<sizeof(T), std::alignof(T)>::type buffer_type;
    int len;
    buffer_type b[1];
};
template<class T> var::var(int l)
    :len(0)
{
    try {
        for (len=0; len<l; ++len)
            new(ptr(i))T();
    }catch(...) {
        for (--len ; len>=0; --len)
            ptr(i)->~T();
        throw;
    }
}
template<class T> var::~var()
{
    for ( ; len>=0; --len)
        ptr(i)->~T();
}
template<class T> std::shared_ptr<var> var::make_var(int num_b)
{
    const extra_bytes = (num_b ? num_b-1 : 0)*sizeof(int);
    auto buffer = std::make_unique(new char[sizeof(var)+extra_bytes ]);
    auto ptr = std::make_unique(new(&*buffer)var(num_b), [](var*p){p->~var();});
    std::shared_ptr<var> r(ptr.get(), [](var* p){p->~var(); delete[]((char*)(p));});
    ptr.release();
    buffer.release;
    return std::move(r);
}

これはテストされていないため、おそらくコンパイルすらされておらず、バグがある可能性があります。私は通常使用しますstd::unique_ptrが、適切なスタンドアロンのデリータを作成するには怠惰すぎてunique_ptr、デリータがラムダの場合、関数から戻るのは困難です。このようなコードを使用したい場合は、適切なスタンドアロンの削除ツールを使用してください。

于 2013-11-14T05:50:28.743 に答える
2

これはあなたの質問に直接答えているわけではありませんが、C++ でのより良い実践は、この種の可変長配列に STL ライブラリを使用することであることを指摘します。 .

class var
{
...
private:
  int a;
  std::vector<int> b; // or use std::deque if more to your liking
};

これで、他のクラスと同じように新しいものにすることができます。

var* myvar = new var;

また、メモリを明示的に割り当てることなく、古い型配列と同じように使用できます (ただし、ほとんどの ++ プログラマーはそうではありません)。

myvar->b[0] = 123;
myvar->b[1] = 123;
myvar->b[2] = 123;
于 2013-11-14T04:20:19.873 に答える
1

はい、できますが、配列メンバーとして宣言することはできません。参照を使用できます:

struct s {
    int ( & extra_arr )[];

    s() : extra_arr( reinterpret_cast< int (&)[] >( this[1] ) {}
};

実際には、これはポインターに相当するストレージを使用しますが、理論的にはその必要はありません。このクラスは POD ではありません。これは、理論と実践の違いによるものです。


reinterpret_castまたは、を非静的メンバー関数に入れることもできます。

struct s {
    int ( & get_extra() )[]
        { return reinterpret_cast< int (&)[] >( this[1] ); }

    int const ( & get_extra() const )[]
        { return reinterpret_cast< int const (&)[] >( this[1] ); }
};

現在、アクセスには関数呼び出し構文が必要です (インライン化により、デバッグ ビルド以外のマシン コードの区別がなくなります) が、無駄なストレージはなく、オブジェクトは POD ルールの他の例外を除いて POD になります。

このように ABI を少し調整するだけで、#pragma pack完全な C バイナリ互換性を得ることができます。いずれにしても、シリアライゼーション アプリケーションでは、このような調整が必要になることがよくあります。

また、これは const-correctness をサポートしていますが、前のソリューションでは const オブジェクトの変更が許可されていました (配列が同じオブジェクトの一部であることを認識していないため)。

ボイラープレートは、CRTP 基本クラス (C++11 では派生クラスを POD にすることもできます)、または C++ アクセサーまたは C フレキシブル メンバーのいずれかを定義するために展開されるプリプロセッサ マクロに一般化できます。


これらの解決策はどちらも元の C 以外のことをしないことに注意してください。特別なメンバー関数は柔軟な配列をコピーせず、クラスは関数パラメーターまたはサブオブジェクトをサポートできません。

于 2013-11-14T04:52:47.080 に答える
1
template <size_t MAX>
class var
{
   ...
private:
  int a;
  int b[MAX];
};

各テンプレートのインスタンス化で、MAX はループで使用できる定数です。次に、任意の長さの変数を作成できます。

var<7> v7;
var<100> v100;

またはそれらをtypedefする

typedef var<10> myVar;
于 2014-09-19T08:34:09.793 に答える
0

わかりました-(よくわからないので、これに疑問を投げかけません)現在の回答から、現在、過剰な割り当てよりも良い方法はないように思われるので、これがメンテナンスの賢明さに役立つかどうか疑問に思っていました:

template <class BASE, class T>
class dynarray
{
public:
    BASE base;
    const size_t size;
    T data[1]; // will be over allocated

    static dynarray * create(size_t data_size)
    {
        return new(data_size) dynarray(data_size);
    }
    void operator delete(void *p)
    {
        ::operator delete(p);
    }
private:
    void * operator new (size_t full_size, size_t actual)
    {
        if (full_size != sizeof(dynarray))
        {
            // inheritence changed size - allocate it all
            return ::operator new(sizeof(dynarray));
        }

        return ::operator new(sizeof(dynarray) + (actual-1)*sizeof(T));
    }
    void operator delete(void *p, size_t) // matching delete
    {
        ::operator delete(p);
    }
    dynarray(size_t data_size) : size(data_size)
    {
    }
};

使用法は少し堅苦しいですが、おそらくより良いでしょう:

typedef dynarray<double,int,27> dyn;
dyn * x=dyn::create(7);
x->data[5]=28;
x->base=5.3;

編集: 実装を割り当ての下から割り当ての上に変更しました

于 2013-11-14T06:36:53.620 に答える