#Can I instantiate a class, and then turn it into a unique_ptr?
104 messages · Page 1 of 1 (latest)
When your question is answered use !solved to mark the question as resolved.
Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.
Okay, so some context for what I've already done. I go to cppreference and look at what I think is the declarations here. And I really squint my eyes trying to tell what I should pass to it in this case, but I still don't find it apparent. It takes... Args?
It's supposed to make it a unique ptr, but it takes Args. I don't know what to make of that.
And as for this, I dunno if this is my usecase.
This is the same input as with when you just use std::unique_ptr to make a unique ptr
If you have an existing object with a copy constructor, you can pass it there.
make_unique basically takes constructor arguments.
Does this look right?
Where in cppreference does it show or indicate that you can do this?
by knowing what the type you give to unique_ptr can do, and by reading the description of the right version of make_unique that you want to use
finding that right version is non-trivial if you don't quite know which one you want/are looking for, and are unfamiliar with templates/variadic templates, so sometimes it's just faster to read the descriptions
in this specific case you want version 1
the generic description for all versions states
Constructs an object of type T and wraps it in a std::unique_ptr.
the part specific to version 1 states
Constructs a non-array type T. The arguments args are passed to the constructor of T. This overload participates in overload resolution only if T is not an array type. The function is equivalent to:
unique_ptr<T>(new T(std::forward<Args>(args)...))
so the "Args"/"args" things essentially boils down to "take anything, dumps everything to T"
which is where "knowledge of T" matters
this one looks like the one that would've been invoked in my test program tho.
here T is int, int can be copied, so you can just pass in an int value and it'll be copied into the int created/managed by unique_ptr
those are for array forms, that's not what you've tried to use
those are for when you do int* ptr = new int[10] and delete[] ptr except for unique_ptr
the clue is in the green text to the side
actually reread the screenshot and I missed the mark, why do you think those would be called in your case?
I kinda just assumed you looked at the versions that take an integer
I assumed an integer would suffice for my example
So basically, for a class, I would do this?
std::unique_ptr<ClassName> u_instance = std::make_unique<ClassName>(ClassName::Create(...));
Yes, though this obviously makes a copy or move.
Create is a static function
Why not make Create itself return unique_ptr if that's what you are going to use it with?
You're just seeing one usecase of Create, I don't want everything Create produces to be unique_ptr as far as I know.
well I meant that there are overloads of make_unique that specifically take an "int", so I was expecting you to say that those overloads looked like more likely candidate than the first one
so I was wondering why you pointed at overloads 3, 6 and 4 as more likely candidates
and it depends what create returns, if it returns by value then you'll end up doing a copy/move
if you want to 100% kick out the potential copy/move and still manage the thing in a unique_ptr there is a way but it's fairly annoying to type out everytime
Does
make_unique copy implicitly if it is passed a value?
so you'd probably actually just wrap the thing in its own function
well you're missing the thing
It can call the copy constructor.
essentially you'd use make_unique instead of create
make_unique forward everything to the "constructor"
so if you pass in a value of the object, it'll copy it
I wouldn't because calling Create was the thing I wanted to do.
if you pass constructor arguments it'll try to invoke the constructor that takes those arguments
I don't know why you want to do that, nor why the thing has to end up in a unique_ptr, I'm just stating that they partially do overlaping duty
create arguably uses a constructor of the type
maybe you have extra logic and setup of your own
make_unique is intended to call the constructor in your stead
mixing both is awkward
like you'd be better off spinning your own version of make_unique instead
They don't really do an overlapping duty. Create makes a function call that needs to be done after the construction of the object. It's not smart-pointer related at all.
if you're fine with copying/moving you can still just use make_unique + create, but if you cannot afford the copy/move then you'll have to do something else
they do overlapping duty because they both want to call the constructor
you cannot call the constructor two times for the one unique object
if you end up calling the constructor twice, that means you have two objects at some point
and the question I'm asking you is if you're fine with that or if you aren't fine with that
This is what Create actually does.
static Create(Args&& ... args){
T instance = T(std::forward<Args>(args)...);
instance.OnCreate();
return instance;
}
there are contexts where you mustn't have two objects
that's plenty awkward
And make_unique does the same as the second line - just new in front.
why is OnCreate not part of the constructor itself
also that
that's why it's overlapping duty, because both of them want to do the first line
You can't necessarily do everything you want to in the constructor.
except only one of them can
no, but excluding virtual shenanigans calling a function is typically fair game
but if you need this setup, you still have overlapping duty, and the "nicest" way to create into a unique_ptr without doing a copy/move, is to have a CreateUnique that does the same thing
so
static std::unique_ptr<T> CreateUnique(Args&& ... args) {
return std::unique_ptr<T>{ new T(Create(std::forward<Args>(args)...)) };
}
mandatory copy elision has to be performed in this context so you get to defer everything to Create
Makes sense.
but the syntax is heavy as all hell so about no one would type this everytime, especially since it's not especially clear that it is exception-safe
so normally you'd wrap this into its own function
Is the copying/moving you are talking about implicit? or do you use std::move for example?
calling Create produces a prvalue so move is kicked off automatically without std::move
though if your type doesn't have move semantics that would be a copy
and if your type isn't movable or copyable then it's an error
which is why you have to ensure "copy elision" is eligible
so be in a situation where the language requires that no copy/move is done
I'll have to mention that this is a C++17 thing
before C++17 you'd have to have an eligible copy/move constructor, and elision would be an allowed optimization
since C++17, there are contexts where elision is mandatory and the copy/move constructors are not required to exist
I'm using C++17
then you can use this syntax to have copy elision
the new T(Create(...)) only creates one object, on the "heap" (no copy/move involved, not an optimization that skips the copy/move but actually something fundamentally required by language semantics)
the resulting pointer is then given to unique_ptr
unique_ptr is given a new T created with Create, it's allocated on the heap as required by language semantics. Resulting pointer is given to unique_ptr.
Copy elision results in no copying being made. Did I get that right?
And this is a typical example of copy elision?
T x = T(T(f())); // x is initialized by the result of f() directly; no move
I wouldn't say "typical usage", more like an example to illustrate what it does/is meant to do/is meant to avoid/optimize
about no one would intentionally write this kind of code
but if you're writing generic code there are contexts where you can indirectly end up writing this kind of thing
and if copy elision wasn't a thing at all, then you would potentially have to call f, copy/move into a temporary, copy/move into another temporary, copy/move into x
well admittedly you probably would kick out one copy/move cause one of them is the initializer but that's kinda whatever
you get the idea, most likely
Can I also do this directly without wrapping it in a function, technically?
std::unique_ptr<ClassName>{ new ClassName(ClassName::Create()) };
That sounds like a lot of steps that I don't understand fully.
never said you couldn't, but it's much riskier and considered bad style
(also it's missing a closing parenthesis)
before C++17, there was a potential leak if you were to do something like
class ComplicatedType { ... };
void foo(std::unique_ptr<ComplicatedType> lhs, std::unique_ptr<ComplicatedType> rhs);
int main()
{
foo(std::unique_ptr<ComplicatedType>{ new ComplicatedType }, std::unique_ptr<ComplicatedType>{ new ComplicatedType });
}
at least iirc the rule related to the issue where changed in c++17
make_unique was in part introduced to solve the problem, and in part related to that plus all the issues and errors surrounding invalid/bad use of new, it's just generally recommended not to do that and go through make/create functions that wrap the thing properly
just double checked and it was indeed changed in c++17, so normally the risk would have been eliminated, but it's still a style issue
So the part that's different is that returning it is the part that resolves the issue?
it's more complicated than that
but I don't have time right now
the leak is due to interleaving the results/effects of the expressions, which cannot happen if you make a proper function call
and also exceptions
Alright. Thank you.