可変長配列メンバ

http://d.hatena.ne.jp/yotto-k/20071216#1197816102
元ネタはこちら(っとid:yotto-kさんの代わりにトラバ)
http://d.hatena.ne.jp/higepon/20071215/1197733211


この問題、確かBoostMLでDavid Abrahams氏がポストしてたよなぁと思ったら、id:Cryoliteさんの記事
http://d.hatena.ne.jp/Cryolite/20051102#p1
経由で、
http://thread.gmane.org/gmane.comp.lib.boost.devel/42152
を発掘しました。
読み返してみても結論らしい結論は出ていないみたいです。
この議論の中で、

struct s { int n; double d[1]; };

だと、本来dの添え字は0しかありえないので、

double func(struct s* p, size_t i)
{
    return p->d[i];
}

コンパイラ

double func(struct s* p, size_t i)
{
    return p->d[0];
}

と最適化する可能性が指摘されています。
ただし、システムヘッダでこの技法が使われている環境(Windowsなど)なら実質的にこの最適化は不可能なので、まともなコンパイラなら動くと思います。
また、コンパイラの拡張でサイズ0の配列をサポートしている処理系なら、明示的にこの最適化を回避できるはずです。


このように、サイズ1(または0)の配列ではグレーな部分が多いんですが、ISO C(C99)なら言語のサポートがあって、

struct s { int n; double d[]; };
struct s* p = malloc(sizeof(struct s) + sizeof(double)*n);

でOKです。
最新のPOSIX規格はC99を取り込んでいるので、dirent構造体などはこの機能を利用しています。


C++でより安全に、

  • 添え字が配列のサイズを超えないようにする
  • POD以外のメンバにも対応する

などを考えると、こんな感じになります。

#include <boost/type_traits/alignment_of.hpp>
#include <boost/noncopyable.hpp>
#include <boost/scoped_array.hpp>

template<class T>
class scoped_destroy : private boost::noncopyable
{
public:
    explicit scoped_destroy(T* p) : ptr_(p)
    {
    }

    ~scoped_destroy()
    {
        if (ptr_)
            ptr_->~T();
    }

    void release()
    {
        ptr_ = 0;
    }

private:
    T* ptr_;
};

template<class T>
class scoped_placement_array : private boost::noncopyable
{
public:
    explicit scoped_placement_array(T* p) : ptr_(p), size_(0)
    {
    }

    void construct_next()
    {
        new (static_cast<void*>(ptr_ + size_)) T;
        ++size_;
    }

    ~scoped_placement_array()
    {
        T* p = ptr_ + size_;
        while (p != ptr_)
            (--p)->~T();
    }

    void release()
    {
        size_ = 0;
    }

private:
    T* ptr_;
    std::size_t size_;
};

template<class Header, class Size, Size Header::* SizePtr, class T>
class ptr_with_flexible_array_member
{
private:
    static const std::size_t member_alignment
        = boost::alignment_of<T>::value;

    // 可変長配列メンバのオフセットは、
    // sizeof(Header)以上でアラインメント要求を満たす(最小)値
    static const std::size_t member_offset
        = (sizeof(Header) + (member_alignment - 1))
        / member_alignment * member_alignment;

public:
    explicit ptr_with_flexible_array_member(Size size)
        : buffer_(new char[member_offset + sizeof(T)*size])
    {
        scoped_destroy<Header> h(
            new (static_cast<void*>(buffer_.get())) Header);

        scoped_placement_array<T> m(
            reinterpret_cast<T*>(buffer_.get() + member_offset));

        for (Size i = 0; i < size; ++i)
            m.construct_next();

        // コミット
        header_ptr()->*SizePtr = size;
        h.release();
        m.release();
    }

    ~ptr_with_flexible_array_member()
    {
        Header* head = header_ptr();
        T* data = data_ptr();

        T* p = data + head->*SizePtr;
        while (p != data)
            (--p)->~T();

        head->~Header();
    }

    Header& operator*()
    {
        return *header_ptr();
    }

    const Header& operator*() const
    {
        return *header_ptr();
    }

    Header* operator->()
    {
        return header_ptr();
    }

    const Header* operator->() const
    {
        return header_ptr();
    }

    T& operator[](std::size_t i)
    {
        return data_ptr()[i];
    }

    const T& operator[](std::size_t i) const
    {
        return data_ptr()[i];
    }

private:
    boost::scoped_array<char> buffer_;

    Header* header_ptr() const
    {
        return reinterpret_cast<Header*>(buffer_.get());
    }

    T* data_ptr() const
    {
        return reinterpret_cast<T*>(buffer_.get() + member_offset);
    }
};

// 以下、テスト
#include <iostream>
#include <stdexcept>
#include <string>

struct hoge
{
    int size;
};

// デストラクタ確認用
class bye
{
public:
    bye() : value_(-1) {}

    ~bye()
    {
        std::cout << "bye " << value_ << std::endl;
    }

    void set(int n)
    {
        value_ = n;
    }

private:
    int value_;
};

struct fuga
{
    int size;
    bye b;

    fuga()
    {
        b.set(100);
    }
};

// 2つ目の生成で失敗する場合用
class singleton
{
public:
    singleton()
    {
        if (initialized)
            throw std::runtime_error("cannot create any more 'singleton'");
        initialized = true;
    }

    ~singleton()
    {
        std::cout << "singleton::~singleton()" << std::endl;
        initialized = false;
    }

private:
    static bool initialized;
};

bool singleton::initialized = false;

int main()
{
    try
    {
        ptr_with_flexible_array_member<hoge,int,&hoge::size,int> h(10);
        for (int i = 0; i < h->size; ++i)
            h[i] = i;
        for (int i = 0; i < h->size; ++i)
            std::cout << h[i] << std::endl;

        // POD以外もOK
        ptr_with_flexible_array_member<hoge,int,&hoge::size,std::string> h2(3);
        for (int i = 0; i < h2->size; ++i)
            h2[i].assign(3, i["abc"]);
        for (int i = 0; i < h2->size; ++i)
            std::cout << h2[i] << std::endl;

        // 配列の後ろ側からデストラクタが呼ばれるかのテスト
        ptr_with_flexible_array_member<fuga,int,&fuga::size,bye> h3(3);
        for (int i = 0; i < h3->size; ++i)
            h3[i].set(i);

        // 2個目のsingleton生成で例外が発生するが、1個目は解体されることのテスト
        ptr_with_flexible_array_member<hoge,int,&hoge::size,singleton> h4(2);
    }
    catch (const std::exception& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
    }
}

/* 実行結果
0
1
2
3
4
5
6
7
8
9
aaa
bbb
ccc
singleton::~singleton()
bye 2
bye 1
bye 0
bye 100
Error: cannot create any more 'singleton'
*/

ヘッダ部にサイズ用のメンバがあることが前提になっています。
例外安全性とか考えると結構面倒で、実際問題ここまでする意義はあまり感じません。
おとなしく配列をvectorに置き換えて、参照の局所性が必要ならboost::pool_allocatorなどのメモリプールを利用した方が現実的だと思います。


ちなみに

5.3.4/10
For arrays of char and unsigned char, the difference between the result of the new-expression and the address returned by the allocation function shall be an integral multiple of the most stringent alignment requirement (3.9) of any object type whose size is no greater than the size of the array being created.

ISO/IEC 14882/2003

ということで、

char* buf = new char[sizeof(T)];
T* p = new(buf) T;

はアラインメントの問題なしです。
自分はこういう場合、これまで::operator new()を使ってましたが、これからはこっちを使うことにします。
こっちならscoped_arrayが使えますし。