Представьте, что у меня есть класс без открытого конструктора, но со статическим методом фабрики или компоновщика, и размер созданного объекта зависит от аргументов, передаваемых фабрике (или компоновщику).
Есть ли способ создать общий (или уникальный) указатель на такой объект, используя std::make_shared
(или же std::make_unique
)?
Для любого предлагаемого полиморфизма я предпочитаю шаблоны виртуальным методам.
Считайте, что у меня есть класс без публичного конструктора …
Есть ли способ создать общий (или уникальный) указатель на такой объект, используя
std::make_shared
(или жеstd::make_unique
)?
Там нет такого способа. std::make_shared
а также std::make_unique
требует публичного конструктора.
Вместо этого вы можете создать общий (уникальный) указатель без удобной функции make:
// within the factory
return std::shared_ptr<T>(new T(size));
PS. Стандартная библиотека C ++ 11 не имеет std::make_unique
,
Если у вас есть фабричный метод, который создаст объект в блоке памяти, который вы ему предоставите, плюс метод, который сообщает вам, сколько места вам понадобится, вы можете сделать это.
Я собираюсь свободно использовать C ++ 14, добавить свой собственный typename
а также ::type
если вам действительно нужен C ++ 11.
Сначала мы предполагаем, что у нас есть это:
struct some_args {
// whatever
};
std::size_t how_big_is_X( some_args );
X* make_X( some_args, void* buffer );
с вышеизложенным, я могу сделать что-то функционально эквивалентное make_shared
,
template<std::size_t Sz, std::size_t align=alignof(void*)>
struct smart_buffer_t {
void(*dtor)(void*) = 0;
std::aligned_storage_t< Sz, align> data;
template<class T, class...Args>
T* emplace(Args&&...args) {
return ctor( [&](void* pdata) {
::new( pdata ) T(std::forward<Args>(args)...);
} );
}
template<class F>
T* ctor( F&& f ) {
std::forward<F>(f)( (void*)&data );
dtor = [](void* ptr){
static_cast<T*>(ptr)->~T();
};
return static_cast<T*>((void*)&data);
}
~smart_buffer_t() {
if (dtor) dtor(&data);
}
};
template<class T, std::size_t Sz, std::size_t Algn=alignof(void*), class F>
std::shared_ptr<T>
make_sized( F&& f ) {
auto pbuff = std::make_shared<smart_buffer_t<Sz, Algn>>();
T* r = pbuff->ctor( std::forward<F>(f) );
return {r, pbuff}; // aliasing ctor
}
теперь у нас есть:
template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t I>
using pow_t = index_t< (1<<I) >;
std::shared_ptr<X>
make_shared_X( some_args args ) {
std::size_t Sz = how_big_is_X(args);
using pmaker = std::shared_ptr<X>(*)(some_args);
using maker_maker = [](auto Sz){
return +[](some_args args) {
return make_sized<X, Sz>([&](void* ptr){
return make_X(args, ptr);
});
};
};
static const pmaker table[] = {
maker_maker(pow_t<0>{}),
maker_maker(pow_t<1>{}),
maker_maker(pow_t<2>{}),
// ...
maker_maker(pow_t<63>{}), // assuming 64 bit size_t.
};
std::size_t i = 0;
while(Sz > (1<<i))
++i;
return table[i](args);
}
или что-то подобное.
Код не проверен. Он выделяет степень 2 в или больше, чем требуют ваши аргументы. Но объект создается в том же распределении, что и блок подсчета ссылок.
Можно использовать любой ряд вместо степеней 2, но размер таблицы должен быть достаточно большим, чтобы обрабатывать максимально возможное возвращаемое значение из how_big_is_X
,
#include <memory>
#include <vector>
#include <iostream>
template <typename T>
std::unique_ptr<T> buildObjectWithSize(std::size_t size) {
return std::unique_ptr<T>{T::buildObject(size)};
}
class MyObject {
public:
static MyObject* buildObject(std::size_t size) {
return new MyObject(size);
}
int& operator[](int i) {
return int_vector[i];
}
private:
MyObject(std::size_t size)
: int_vector(size) {
}
std::vector<int> int_vector;
};
int main () {
constexpr unsigned INT_VECTOR_SIZE{3U};
std::unique_ptr<MyObject> my_object_up{buildObjectWithSize<MyObject>(INT_VECTOR_SIZE)};
(*my_object_up.get())[1] = 5;
std::cout << (*my_object_up.get())[1] << '\n';
MyObject &my_object_ref = *my_object_up.get();
my_object_ref[2] = 3;
std::cout << my_object_ref[2] << '\n';
}