#Can I instantiate a class, and then turn it into a unique_ptr?

104 messages · Page 1 of 1 (latest)

lusty viper
#

I'm not sure if there's much I can add to this question. I guess I might wanna do something like
ClassName instance = ClassName::Create(...);
std::unique_ptr<ClassName> u_instance = std::unique_ptr<ClassName>(&instance);

Altho pretty sure this segfaults, have tested with an integer.

rigid irisBOT
#

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.

tardy pivot
#

You use std::make_unique

#

Or the constructor taking pointer (should be on the heap).

lusty viper
# tardy pivot You use std::make_unique

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

tardy pivot
#

make_unique basically takes constructor arguments.

lusty viper
#

Does this look right?

#

Where in cppreference does it show or indicate that you can do this?

leaden pike
# lusty viper 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

leaden pike
# lusty viper Okay, so some context for what I've already done. I go to cppreference and look ...

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

lusty viper
#

this one looks like the one that would've been invoked in my test program tho.

leaden pike
leaden pike
#

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

leaden pike
#

I kinda just assumed you looked at the versions that take an integer

lusty viper
#

So basically, for a class, I would do this?

std::unique_ptr<ClassName> u_instance = std::make_unique<ClassName>(ClassName::Create(...));

tardy pivot
#

Yes, though this obviously makes a copy or move.

lusty viper
#

Create is a static function

tardy pivot
#

Why not make Create itself return unique_ptr if that's what you are going to use it with?

lusty viper
#

You're just seeing one usecase of Create, I don't want everything Create produces to be unique_ptr as far as I know.

leaden pike
# lusty viper I assumed an integer would suffice for my example

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

leaden pike
#

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

lusty viper
#

Does

make_unique copy implicitly if it is passed a value?

leaden pike
#

so you'd probably actually just wrap the thing in its own function

#

well you're missing the thing

tardy pivot
leaden pike
#

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

lusty viper
leaden pike
#

if you pass constructor arguments it'll try to invoke the constructor that takes those arguments

leaden pike
#

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

lusty viper
#

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.

leaden pike
#

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

leaden pike
#

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

lusty viper
#

This is what Create actually does.

static Create(Args&& ... args){
    T instance = T(std::forward<Args>(args)...);
    instance.OnCreate();
    return instance;    
}
leaden pike
#

there are contexts where you mustn't have two objects

tardy pivot
leaden pike
#

why is OnCreate not part of the constructor itself

leaden pike
lusty viper
#

You can't necessarily do everything you want to in the constructor.

leaden pike
#

except only one of them can

leaden pike
#

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

lusty viper
#

Makes sense.

leaden pike
#

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

lusty viper
#

Is the copying/moving you are talking about implicit? or do you use std::move for example?

leaden pike
#

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

lusty viper
#

I'm using C++17

leaden pike
#

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

lusty viper
#

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
leaden pike
#

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

lusty viper
lusty viper
leaden pike
#

never said you couldn't, but it's much riskier and considered bad style

#

(also it's missing a closing parenthesis)

lusty viper
#

Yup

#

Where's the risk?

leaden pike
#

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

leaden pike
lusty viper
#

So the part that's different is that returning it is the part that resolves the issue?

leaden pike
#

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

lusty viper
#

Alright. Thank you.