1

I am reading C++ Primer and find these kinda confusing:

The reset member is often used together with unique to control changes to the object shared among several shared_ptrs. Before changing the underlying object, we check whether we’re the only user. If not, we make a new copy before making the change:

if (!p.unique())
    p.reset(new string(*p)); // we aren't alone; allocate a new copy
*p += newVal; // now that we know we're the only pointer, okay to change this object

What does the emphasized text mean in the quoted text above? So confused.

Update:

After reading the text again, I find out that I may miss something.

So according the code above, let's assume there are 2 shared_ptr (one is p mentioned here) pointing to the original dynamic memory object let's say A. Then if I want to modify object A, I allocate a new dynamic memory with the copy value of A(new string(*p)), assign it to p, let's say B. So eventually A is not modified, but only create a copy of modified version of A?

enter image description here

Why not directly do *p += newVal;? And why is it related to Copy-on-write mentioned in answers? I mean, there's no extra copy operation needed. All shared_ptr originally points to dynamic memory object A. Only 1 object.


Screenshot that may supply a little bit more context: enter image description here

Rick
  • 7,007
  • 2
  • 49
  • 79
  • 4
    It just looks like it doesn't want to modify the value if its shared. This isn't a requirement of shared_ptr, its simply a design decision. This behavior might be needed if there are other threads to consider. – kmdreko Jul 11 '18 at 03:37
  • @vu1p3n0x thank you for helping :D – Rick Jul 11 '18 at 04:00
  • 2
    This code is not thread-safe – M.M Jul 11 '18 at 04:24
  • COW implementation ? – Jarod42 Jul 11 '18 at 10:13
  • @Jarod42 Why is it related to COW sir? I've updated my question. – Rick Jul 11 '18 at 12:58
  • @Rick Let's assume that we have (non std) string ''a'' and then making string ''b'' from ''a'': string b(a). Semantically ''b'' is a copy of ''a'' and when we modify ''b'', ''a'' should stay untouched. But there is can be a case when ''b'' is never modified and memory allocation+copying to have ''b'' was redundant. COW allows to avoid unnessary allocation+copy in such a case. ''b'' shares memory chunk with ''a'' until ''b'' is actually have to be modified. – wtom Jul 11 '18 at 14:12
  • @wtom Yes. I understand the concept about COW under this circumstance. But since all `shared_ptr`s points to only **1** allocated dynamic memory object. Why modifying a pointed object would concern about extra copying operation? I mean, I think there's no extra "redundant" operation. You "directly" change the pointed object as like through an ordinary pointer. – Rick Jul 11 '18 at 14:21
  • @Rick they point to same object until reset(new string(*p)) on one of shared_ptr is called. Then they point to different memory chunks. – wtom Jul 11 '18 at 14:31
  • @wtom Yes, I agree that, see my newly added draft. So after updating, I am asking why reset it? Why make a new copy instead of changing the value directly? I have no idea about that. So for now, *codekaizer* 's answer seem to make a valid point. – Rick Jul 11 '18 at 14:36
  • @Rick because *a* should stay untouched. *b* pretends to be an independent copy of *a* – wtom Jul 11 '18 at 14:39
  • @wtom Yes. That might be the only reason behind this code (`a` stay untouched). But it isn't related to COW. It's sth like *codekaizer* said. Prevent data race, new concept to me too. – Rick Jul 11 '18 at 14:41
  • Hmmmm. Look at all the _trouble_ this 'example' has caused. I think we should send them a letter of complaint. – Paul Sanders Jul 17 '18 at 12:54

3 Answers3

4

I think authors of the book described here how Copy-on-write paradigm can be implemented using shared_ptr. As mentioned in comments before "this isn't a requirement of shared_ptr, its simply a design decision".

wtom
  • 565
  • 4
  • 12
  • Yes, I can see there could be value in that. +1 – Paul Sanders Jul 11 '18 at 09:05
  • @PaulSanders @ wtom I've updated the question. Would you like to review it and probably explain how it is related to *Copy-on-write*? I've read the Wikipedia page about it, but doesn't seem to be related. :D – Rick Jul 11 '18 at 12:57
  • Furthermore, the usage appears to have a race condition. Right after deciding it's unique, it could suddenly become non-unique, or the other way around. Unless you do some locking or are certain there's no way for anyone else to get at that pointer, you are rolling the dice. You may be able to do something like this with a `std::atomic>`, but I doubt it. I think you'd really want to use a mutex. – Ben Jul 11 '18 at 13:05
  • @Rick I will indeed, just as soon as I have time. Don't worry about it (or race conditions) right now. – Paul Sanders Jul 11 '18 at 13:06
  • @Rick I've done that now, I hope it tells you what you wanted to know. Follow-up questions welcome. – Paul Sanders Jul 16 '18 at 22:00
  • @wtom Or maybe, on reflection, I can't, certainly not in a _primer_ anyway. See my rewritten answer for more details / analysis. – Paul Sanders Jul 16 '18 at 22:02
  • @Ben A race condition is not a bug. – curiousguy Jul 20 '18 at 01:42
  • @curiousguy, depending on your definitions, perhaps. But in this case, a race condiction leads directly to a bug, right? https://en.wikipedia.org/wiki/Race_condition – Ben Jul 23 '18 at 13:24
  • @Ben Can you have any non trivial MT program without any race condition? Why do you even use a mutex if multiple treads don't race on trying to lock it? Same with any atomic primitive, without a race, it doesn't serve any purpose. – curiousguy Jul 23 '18 at 15:17
  • @curiousguy From Wikipedia "A race condition or race hazard is the behavior of an electronics, software, or other system where the output is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when events do not happen in the order the programmer intended." So while multithreaded programs are typically "racing" to get their work done, tools like muteness exist to avoid race conditions: that is, to have the output not depend on which thread "wins the race". – Ben Jul 24 '18 at 17:15
  • @Ben Using a mutex correctly does **not** avoid a race condition, it avoids a bug. **You are always racing on obtaining the mutex.** As the quote says "_a bug when events do not happen in the order the programmer intended_". If two threads must run actions in a pre-defined order, a mutex alone will not solve the problem, as it can only make sure actions are not done at the same time. **The mutex is the family of synchronisation primitives that does not impose an order.** Semaphore, Windows' events... or the good old mutex+cv: many primitives can be used to impose an order. – curiousguy Jul 24 '18 at 19:38
  • From WP: "_A critical race condition occurs when the order in which internal variables are changed determines the eventual state that the state machine will end up in_" It's up to you to ensure that the end result will not depend on the result of races: for any synchronization primitive, any execution of the program has a well defined order of operations on the synchronization object: the result of racing on the mutex. The program must be proved correct for any such ordering. – curiousguy Jul 24 '18 at 19:42
  • I guess we disagree only in our semanitcs. The usage I feel I’ve always seen is that a program “has a race condition” if there is a bug that comes from a race. I don’t think I’ve heard anyone say “This program has a race condition but through the use of synchronization primitives, there is no bug.” I would think that once the output doesn’t depend on races, then there is no race condition; otherwise it seems like a distinction without a difference. – Ben Jul 25 '18 at 11:53
3

For you are only allowed to modify the shared_ptr and not the objects they refer to. This is to prevent data races.

From util.smartptr.shared/4:

For purposes of determining the presence of a data race, member functions shall access and modify only the shared_­ptr and weak_­ptr objects themselves and not objects they refer to.

Changes in use_­count() do not reflect modifications that can introduce data races.

For the reset() member function:

void reset() noexcept;

Effects: Equivalent to shared_­ptr().swap(*this).

Joseph D.
  • 11,804
  • 3
  • 34
  • 67
  • Thank you. So same as the first question comment mentioned, it's merely a *design decision*, not a grammar or language usage correction stuff right? – Rick Jul 11 '18 at 03:58
  • @Rick, I agree it is a *design decision*. – Joseph D. Jul 11 '18 at 03:59
  • 1
    Sorry, but I don't buy your opening paragraph at all. The relevant phrase from the section of the standard that you quote is: _For purposes of determining the presence of a data race_. That doesn't mean that you can't mutate the object through the shared pointer. They're just saying that if you do that from multiple threads at the same time then you're on your own (so guard it with a mutex or something). – Paul Sanders Jul 11 '18 at 08:48
3

Update: Substantially revised, based on new knowledge.


Short answer (to the title of your question): you don't. What C++ primer are you reading? No way is the example you quote there primer material.

The whole idea behind smart pointers is that they 'just work', once you understand them properly, and the author of the passage has pulled a stunt here which would rarely, if ever, be used in practice.

He appears to be trying to describe some sort of oh-so-slightly-weird copy-on-write mechanism implemented in software, but he has clearly bamboozled the OP and no doubt most of the rest of his readership in doing so. It's all a bit silly and it's just not worth trying to understand why they present it as they do (or, indeed, just what it is supposed to do in the first place). Like I say, it has no place in a primer (or probably anywhere else).

Anyway, std::shared_ptr::unique() is flawed (it is not threadsafe) and will be going away soon. It should probably never have existed in the first place, don't use it.

One other issue arose during various discussions in the thread, and that is whether it is safe to mutate an object managed by a shared_ptr. Well wherever did you get the notion that it isn't? Of course it is. If you couldn't, many programs simply could not be written at all. Just don't mutate the same object from two different threads at the same time (that's what the standard calls a data race) - that's the only issue. If you do want to do that, use a mutex.

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • I am reading the latest 5th version. I do agree that sometimes this book is really confusing. I also checked *cppreference* and knnew `unique` is somehow decprecated in newer version of C++. – Rick Jul 11 '18 at 04:16
  • It's far worse than confusing. It is dangerous. Maybe someone just had a bad day. (And none of this is _your_ fault). – Paul Sanders Jul 11 '18 at 04:18
  • Ya, newbies like me have to suffer from that. T-T – Rick Jul 11 '18 at 04:19
  • In which case would `unique` possibly give incorrect result? – curiousguy Jul 20 '18 at 01:39