2015-04-04 2 views
1

Предположим, мне нужен массив с переменным размером с бит заголовка вверх, хранящийся в std::shared_ptr. Я мог бы сделать что-то вродеОбъект с переменным размером в shared_ptr

#include <memory> 
using namespace std; 
struct obj { 
    char headerCode; 
    unique_ptr<short[]> data; 
}; 
shared_ptr<obj> make(unsigned len) { 
    return shared_ptr<obj>{new obj{'x', unique_ptr<short[]>{new short[len]}}}; 
} 

Но это привело бы к трем распределений: один для блока shared_ptr управления, один для obj и один для его data. С make_shared возможно, что первые два разделяют некоторую память:

#include <memory> 
using namespace std; 
struct obj { 
    char headerCode; 
    unique_ptr<short[]> data; 
    obj(char headerCode, short data[]) : headerCode(headerCode), data(data) {} 
}; 
shared_ptr<obj> make(unsigned len) { 
    return make_shared<obj>('x', new short[len]); 
} 

С выделением низкого уровня, я могу сделать объект и его доля данных некоторую память:

#include <memory> 
#include <cstdlib> 
using namespace std; 
struct obj { 
    char headerCode; 
    short data[0]; 
}; 
shared_ptr<obj> make(unsigned len) { 
    obj* o = reinterpret_cast<obj*>(malloc(sizeof(obj) + len*sizeof(short))); 
    o->headerCode = 'x'; 
    return shared_ptr<obj>(o, free); 
} 

Являются ли эти два метода, допускаемые стандарт? Если нет, есть ли что-то подобное, что разрешено? Есть ли что-то, что заставляет эту работу соответствовать стандартам со всего лишь одним распределением памяти? Предпочтительно без необходимости хранить распределитель или удалять объект в каждом экземпляре?

+0

Зачем использовать 'reinterpret_cast' для преобразования' void * 'в' obj * '? –

+0

@JonathanWakely: без явного приведения my 'g ++' говорит 'error: недопустимое преобразование из 'void *' в 'obj *' [-fpermissive]'. Но вы правы, «static_cast», вероятно, более уместен. – MvG

+0

Да, конечно, он не будет конвертировать неявно, но 'reinterpret_cast' является кувалдой, здесь это не обязательно. 'static_cast' - это все, что нужно. –

ответ

1

Are these two techniques allowed by the standard?

Первый штраф, второй использует массив нулевой длины в качестве члена (data[0]), которая не является действительным С ++, но поддерживаются как расширение некоторых компиляторов.

Is there something which makes this work in a standards-conforming way with only a single memory allocation?

В настоящее время это довольно трудно без http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3641.html который позволил бы что-то вроде этого:

struct obj { 
    char headerCode; 
    short* data; 
}; 

shared_ptr<obj> make(unsigned len) { 
    auto p = make_shared<char[]>(sizeof(obj) + sizeof(short)*len)); 
    short* s = static_cast<short*>(p.get() + sizeof(obj)); 
    return shared_ptr<obj>(p, ::new(p.get()) obj{'x', s}); 
} 

Это выделяет массив char с достаточно места для obj и массива short, а затем использует размещение новое постройте в этой памяти obj и создайте еще один shared_ptr, который владеет долей владения, принадлежащей массиву char. Когда последний shared_ptr, который владеет памятью, потеряет свою ссылку, массив char будет удален правильно, но дескриптор объекта obj не будет запущен, поэтому важно, чтобы вы не полагались на его деструктор, имеющий какие-либо побочные эффекты (в идеале он будет иметь тривиальный деструктор).

Возможно, вы сможете сделать это сегодня, используя allocate_shared с помощью специального распределителя, который выделяет дополнительное пространство для массива short, но так как вы не знаете, как реализация будет организовывать блок управления и ваш объект в выделенном пространстве довольно сложно разработать, где начинается массив short, и распределитель необходимо сохранить в блоке управления.

+1

Даже если это очень маловероятно, чтобы вызвать проблемы с 'short', не забывайте учитывать выравнивание. В общем, не гарантируется, что 'p.get() + sizeof (obj)' соответствующим образом выровнен для того, что вы хотите разместить после него, хотя практически все платформы будут иметь одинаковые строгие или более строгие требования к выравниванию по сравнению с «короткими» '. – hvd

+0

Да, я это рассматривал, но в этом случае он должен быть безопасным, потому что конечный элемент указателя должен быть выровнен так, чтобы «короткий» мог сразу следовать ему.В общем случае вам может потребоваться добавить (до) 'alignof (T)' bytes в распределение и использовать 'std :: align', чтобы правильно разместить массив' T'. –

Смежные вопросы