trying to understand smart pointers...
-
@Mesrine thanks for the suggestions, but that doesn't compile either (essentially the same error). I think you're right about the unique_ptr not permitting copies. That makes me wonder if I'm misusing the unique_ptr, or if there's a better construct for me to use.
-
@mzimmers said in trying to understand smart pointers...:
thanks for the suggestions, but that doesn't compile either (essentially the same error). I think you're right about the unique_ptr not permitting copies. That makes me wonder if I'm misusing the unique_ptr, or if there's a better construct for me to use.
This is complete bs.
AQString*
andQSharedPointer<QString>
are two comletelty different types so areQList<QString*>
andQList<QSharedPointer<QString>>
- they can not be implicitly converted to each other. -
@Christian-Ehrlicher said in trying to understand smart pointers...:
A QString* and QSharedPointer<QString> are two comletelty different types so are QList<QString*> and QList<QSharedPointer<QString>> - they can not be implicitly converted to each other.
In my example, I have two lists:
QList<QString *> list; QList<std::unique_ptr<QString>> list2;
and I'm trying to append to each:
list.append(qsp); list2.append(qsp2);
So I don't understand your comment.
@SGaist I just used a QString to simplify my example. In my app, I need a list of an class that I'm subclassing. If I just maintain a list of the parent class, I can't add subclasses to the list (at least, I don't see how I can). I'm attempting to use a list of pointers to avoid this problem; if there's a better way, I'd love to hear about it.
-
@mzimmers said in trying to understand smart pointers...:
So I don't understand your comment.
You're right - I did not see there is list and list2
QList needs an copyable type. Use std::vector - it works also with move-only types.
std::vector<std::unique_ptr<QString>> l; l.push_back(std::make_unique<QString>(""));
-
@Christian-Ehrlicher I'd like to pursue using QList, if only for my education. I've added a struct to the exercise:
struct TestStruct { int i; TestStruct() {} TestStruct(TestStruct &ts) {i = ts.i;} }; TestStruct myStruct; std::unique_ptr<TestStruct> *uniquePtr; QList<std::unique_ptr<TestStruct>> list2; uniquePtr = &myStruct; // how to form this? list2.append(uniquePtr); // how to form this?
It seems to be wrong to try to assign to a unique_ptr; I see an assignment operator in the docs, but there's also a proviso:
Copy assignment (4) to a unique_ptr type is not allowed (deleted signature).
So, again, this leaves me thinking I really don't understand how to use unique_ptrs. Can they be modified after they're constructed? -
@mzimmers said in trying to understand smart pointers...:
Can they be modified after they're constructed?
Yes, it's a normal pointer but you can't copy them - only moving is allowed. Your TestStruct is missing the move ctor and move operator.
list2.append(uniquePtr); // how to form this?
As I said - it's not possible with QList as a unique_ptr<T> is not copyable, only movable.
-
sigh I new I should have taken that extra C++ class in night school...
OK, so I think I've added the move c'tor and move operator (though the docs are a little confusing; there's reference to a move assignment operator, which I'm not sure is the same thing). Code looks like this:
struct TestStruct { int i; TestStruct() {} TestStruct(TestStruct &ts) {i = ts.i;} // copy c'tor TestStruct(TestStruct &&ts) { i = ts.i; } // move c'tor TestStruct& operator=(TestStruct&& ts) { return *this; } // move operator }; TestStruct myStruct; std::unique_ptr<TestStruct> *uniquePtr = nullptr; QList<std::unique_ptr<TestStruct>> list; uniquePtr = &myStruct; // how to form this? list.push_back(*uniquePtr); // how to form this?
What am I continuing to do wrong here?
Thanks...
-
@mzimmers said in trying to understand smart pointers...:
What am I continuing to do wrong here?
You still use a QList - as I already told you QList needs a copyable type but std::unique_ptr<T> is not.
-
@Christian-Ehrlicher said in trying to understand smart pointers...:
QList needs a copyable type but std::unique_ptr<T> is not.
Ah, yes. And it appears that QScopedPointer is not either...pity.
So, if I use std::vector, and the make_unique that you mentioned above, I can modify what the unique_ptr references, and push it into the vector and it all works OK:
typedef std::unique_ptr<TestStruct> UniquePtr; UniquePtr uniquePtr; std::vector<UniquePtr> qVector; uniquePtr = std::make_unique<TestStruct>(myStruct); uniquePtr->i = 55; qDebug() << uniquePtr->i; qVector.push_back(std::make_unique<TestStruct>(*uniquePtr)); uniquePtr->i = 555; qDebug() << uniquePtr->i; qVector.push_back(std::make_unique<TestStruct>(*uniquePtr)); uniquePtr->i = 5555; qVector.push_back(std::make_unique<TestStruct>(*uniquePtr)); qDebug() << qVector.at(0)->i << qVector.at(1)->i << qVector.at(2)->i;
Now: I want to reference elements in the vector, probably in a loop. Do I need to create a new unique_ptr for each loop iteration, since I can't do an assignment to it, or is there some other way to do this?
-
@mzimmers said in trying to understand smart pointers...:
Do I need to create a new unique_ptr for each loop iteration, since I can't do an assignment to it, or is there some other way to do this?
I don't understand - you already access the elements (via at() ) and therefore can call the functions of the object (or modify the members of your struct)
-
@Christian-Ehrlicher I'd like to be able to do the following:
for (int i = 0; i < list.size(); i++) { p = list.at(i); p.this = that; p.the_other(); // etc
-
Why do you want to assign something to the local variable
p
? What's the point? -
@Christian-Ehrlicher well, maybe I don't have to. The plan is to use this list in a list model I'm writing. I'll be making temporary variables of my struct, and copying to and from the list. (Plus QML access that I haven't even begun thinking about.) But...maybe I can do it all with newly-created unique_ptrs. I'll try and report back.
BTW: should I consider the use of QScopedPointers instead?
-
Simply store the struct in the container.
-
@mzimmers said:
I'll be making temporary variables of my struct, and copying to and from the list.
The whole point of unique_ptr is that it is unique. It holds ownership of the object. You can't copy (temporary or otherwise) unique_ptr because then you would have two things owning the same object and that would just crash because of double delete.
You can move unique_ptrs, because it moves ownership of the object, so only one pointer still owns the object.
As others mentioned QList does not support move-only types because of implicit sharing. It needs to do copies underneath when a shared data detaches.
QScopedPointer is just a simplified version of std::unique_ptr. Switching one to the other doesn't change anything.
You can store unique_ptr in a std::vector, which does not do implicit sharing and supports move-only types:
std::vector<std::unique_ptr<QString>> pointers; for (int i=0; i < 10; ++i) { pointers.push_back(std::make_unique<QString>("Hello!")); }
or
for (int i = 0; i < 10; ++i) { auto ptr = std::make_unique<QString>("Hello!"); pointers.push_back(std::move(ptr)); // ptr does not point to the object here anymore, it's been moved from. // Code below will compile but is invalid and will likely crash at runtime: ptr->isEmpty(); }
You can then access these pointers like this:
for (int i = 0; i < 10; ++i) { bool use_the_string = pointers.at(i)->isEmpty(); }
or get a reference to the pointer:
for (int i = 0; i < 10; ++i) { const auto& ptr_ref = pointers.at(i); bool use_the_string = ptr_ref->isEmpty(); }
but you can't copy them:
for (int i = 0; i < 10; ++i) { // This won't compile. You can't copy unique_ptrs auto ptr_copy = pointers.at(i); }
If you want to copy the object (not the pointer!) you can do it like this:
for (int i = 0; i < 10; ++i) { QString string_copy = *pointers.at(i); }
-
@mzimmers said in trying to understand smart pointers...:
uniquePtr = &myStruct; // how to form this?
This is not allowed. myStruct sits in stack and will be cleared when your app runs out of its scope. However, uniquePtr will destroy it again when uniquePtr is not used anymore.
-
@Chris-Kawa thanks for the detailed explanation. So, since I can't copy a unique_ptr, do I just copy the contents? Here's what I'd like to do (but I know it won't work):
std::unique_ptr<Equipment> pEquipment; listIndex = getIndex(); if (listIndex == NOT_IN_LIST) { // create a new one. pEquipment = new Equipment(); } else { pEquipment = (&m_list->at(listIndex)); // point to element in list }
This code is in a function that updates my list, either by ultimately modifying an existing element, or adding a new element. When this was just a list of objects (not pointers), I was trying to use a pointer to do double-duty, in order to reduce unnecessary constructor calls.
-
@mzimmers why not std::shared_ptr? A shared pointer does not trigger extra construtor call if it is not dereferenced.
The following code may crash. You may never try to assign a stack memory to a shared or unique pointer.
pEquipment = (&m_list->at(listIndex)); // point to element in list
Also check std::weak_ptr which does not have ownership of the pointer.
-
@mzimmers Smart pointers are not just fancy pants replacements for regular pointers. They are means to reason about ownership.
When using smart pointers you have to think about who's the owner of the object and who just wants to access it.Consider this would compile (it doesn't but bare with me):
pEquipment = (&m_list->at(listIndex));
Who owns the object here, and by owns I mean who is responsible for deleting it?
m_list
orpEquipment
? If they both tried to delete the same object your app would crash.unique_ptr owns the object. You can move the object to another unique_ptr, but you can't make a copy of the pointer.
So to answer what you should do first figure out what you want to achieve in terms of ownership.
If you want to have a single point of ownership use unique_ptr. You can transfer (move) the ownership to another unique_ptr, but only one of them at a time can own the object.
If you want to have multiple points of ownership, in the style of "last leaving the room turns off the light" then you use shared_ptr. All copies of shared_ptr own the object and the last one that is destroyed deletes the object.
If you want to have a single point of ownership and just get access to it sporadically without changing the ownership use unique_ptr to own the object and
unique_ptr::get()
to get a raw pointer to the managed object.If you want shared ownership and means to monitor when the object lives and dies use shared_ptr to hold ownership and weak_ptr to get a non-owning pointer that gets nulled when the object is destroyed by last existing shared_ptr