Reputation: 734
Consider I have a class with no public constructor, but a static factory or builder method, and the size of the created object depends on the arguments passed to the factory (or builder).
Is there a way to create a shared (or unique) pointer to such an object using std::make_shared
(or std::make_unique
)?
For any suggested polymorphism, I prefer templates over virtual methods.
Upvotes: 3
Views: 505
Reputation: 275600
If you have an in-place factory method that will construct the object in a block of memory you provide it, plus a method that tells you how much space you'll need, you can do this.
I'm going to freely use C++14, add your own typename
and ::type
if you really need C++11.
First we assume we have this:
struct some_args {
// whatever
};
std::size_t how_big_is_X( some_args );
X* make_X( some_args, void* buffer );
with the above, I can do something functionally equivalent to 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
}
now we have:
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);
}
or somesuch.
Code not tested. It allocates the power of 2 at or larger than your args demand. But the object is constructed at in the same allocation as the reference counting block.
Any series instead of powers of 2 can be used, but the table size must be large enough to handle the largest possible return value from how_big_is_X
.
Upvotes: 2
Reputation: 238381
Consider I have a class with no public constructor ...
Is there a way to create a shared (or unique) pointer to such an object using
std::make_shared
(orstd::make_unique
)?
There is no such way. std::make_shared
and std::make_unique
require a public constructor.
Instead, you can create the shared (unique) pointer without the convenience "make" function:
// within the factory
return std::shared_ptr<T>(new T(size));
PS. C++11 standard library has no std::make_unique
.
Upvotes: 3
Reputation: 1
#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';
}
Upvotes: -1