|
|
Subscribe / Log in / New account

Ownership and lifetimes

Ownership and lifetimes

Posted Jul 12, 2021 10:02 UTC (Mon) by tialaramex (subscriber, #21167)
In reply to: Ownership and lifetimes by ncm
Parent article: Announcing Arti, a pure-Rust Tor implementation (Tor blog)

So, since it was closest, I consulted Bjarne Stroustrup's "The C++ Programming Language" in its Second Edition from 1991

Stroustrup's long tedious book has no section about ownership. Its awful - presumably machine-generated - index lacks entries for ownership, owning, or related words. Stroustrup's periodic rants about style or philosophy of programming did not seem to (in the time I had to re-read them for this comment) mention the idea. It was, I will thus claim, "Not top of mind". Multiple inheritance was important to Stroustrup. Overloading the pointer reference operator was important. Ownership? Never warrants a mention.

Years later the eventual standard C++ has a smart pointer, auto_ptr. But, auto_ptr doesn't reflect the idea of ownership. If anything it muddies the water, a program with auto_ptrs is confusing, or worse, confused, about what is owned and who by.

So in 2011 (ten years ago) C++ gets unique_ptr, shared_ptr, weak_ptr and so on. These, at last, reflect ownership. The unique_ptr owns the thing to which it is a unique pointer, a weak_ptr explicitly doesn't own anything. But C++ 11 still also has and uses raw pointers, and by this point C++ programmers are also using a lot of references, some of them believe references imply ownership, others the exact opposite. Worse, programs are entitled in C++ to just extract the raw pointer from all these smart pointers except weak_ptr (where for obvious reasons there might sometimes not be a raw pointer) and they do.

Only when you look at work proposed for future C++ do you see the sort of enlightenment about Ownership that Rust has.

And that's only half the story. I titled my comment "Ownership and lifetimes". While C++ grew some ideas about Ownership ten years ago, it still doesn't have Lifetimes. Bjarne has proposals, with straw man implementations for the basic idea, but I'd be astonished if (modulo ongoing concerns that Rust is eating their lunch) his feature makes it into C++ 23 because C++ is today a very Conservative programming language, much more ready to find reasons to do nothing than to change and grow.

Finally, you are concerned that Rust compilation is too slow. One of the reasons C++ in particular is able to be fast is a reckless, almost outright negligent approach to basic engineering. The One Definition Rule. To enable the compiler to process as many translation units as it likes, simultaneously and without communicating, C++ has a rule which says that a C++ program must only have One Definition for symbols visible from multiple translation units. This doesn't feel so bad right? Surely you will get an error from the compiler if you violate this rule? Nope. The compiler can't be sure it will ever notice, and if it doesn't the resulting binary is nonsense. That's why the ODR exists. As a result, C++ has "False positives for the question: Is This A Program?". This is clearly awful software engineering. But it _is_ faster for whatever that was worth.


to post comments

Ownership and lifetimes

Posted Jul 12, 2021 15:30 UTC (Mon) by khim (subscriber, #9252) [Link] (13 responses)

I think ownership is similar to zero and negative numbers. Romans have built very powerful and engineering-savvy (by ancient standards) civilization. Without knowing zero. It took centuries for it to travel from India to the West. Negative numbers took even longer.

Yet today these are something we learn in school or even preschool.

Similar, but more recent, example: two's complement numbers. They were used by EDSAC ¾ century ago. Today they are “no brainer”, everyone uses them.

Yet when in 1989 C standard was written they were not established enough for the C standard to define signed overflow result! It was declared an “undefined behavior” and it was opinions about them started that long feud between C compiler developers and C developers…

In the other post ncm talks about backward compatibility as the reason why Rust feels much safer than C or C++. But what kind of backward compatibility can be affected if you just say “it's no longer an undefined behavior to add MAX_INT and 1”?

On the contrary: the whole reason switch from C/C++ to Rust is contemplated (although not guaranteed, obviously) is because C/C++ compilers treated it's users as “captive audience” for so long: while they retained “theoretical backward-compatibility” (as in: nonexistent code which never triggers obscure “undefined behaviors” which hardware and OS define precisely — should work after compiler update) they broke “practical backward-compatibility” (as in: code which was tested and ironed-out by decades of development was suddenly declared “non-compliant” and was broken).

Rust was never intended as actual C++ replacement (although today a lot of people are actually contemplating if that may be possible or not) but it follows Linus rules to the backward compatibility: The rule is not "we don't break non-buggy user space" or "we don't break reasonable user-space". The rule is simply "we don't break user-space". Even if the breakage is totally incidental, that doesn't help the _user_. It's still breakage. With addon breaking user space is a bit like trees falling in the forest. If there's nobody around to see it, did it really break?

Yes, before they can adopt such a stance they needed to spend almost a decade ironing out problems in the basics of Rust and making sure actual programs which are built against stable Rust features wouldn't contain too many crazy things. C and C++ always allowed crazy programs thus declaring exactly this principle is, probably, not possible.

But complete and utter disregard to expectations and use of C and C++ by real programmers definitely triggered the crisis. Ultimately, after enough breakages, which were dismissed with “hey, standard allows us to do what we do, go fix your 20-years old code” it painted C/C++ compiler developers not as C/C++ developer friends, but as adversaries.

The end result: after long, hard, really arduous process C/C++ compiler developers have finally brought C and C++ to the point where collection of artificial puzzles the language imposes on top of each programming task (all these “don't ever rely on two's complement arithmetic even if it's guaranteed by standard”, “never do arithmetic on nullptr”, “never pass null as reference”, “never use std::move to return value from function… but don't forget it in other places”… and so on) for C++ have become larger than what Rust demands.

Also: when you are doing “bad things” in C/C++ your punishment is often much harsher: instead of getting clear and simple compiler errors you observe how your program turns into pile of goo.

But it's 100% truth: if Rust would ever replace C/C++ then it would be shared effort where achievements of C/C++ compiler developers at making C/C++ insanely, unaffordably, dangerous would be regarded as critical step.

Ownership and lifetimes

Posted Jul 12, 2021 17:51 UTC (Mon) by ncm (guest, #165) [Link] (1 responses)

What a rich fantasy world khim lives in. It hardly makes any contact with objective reality, but clearly has no need to.

It is clever to add links to other postings, and wholly invent remarks to pretend to quote from them. It is even more clever to invent things that must surely have happened before he was born. But it is not clear why anyone should try to tease out the few, lonely correct statements that accidentally appear in the roiling cloud of phantasms presented.

Speaking of lifetimes

Posted Jul 12, 2021 18:05 UTC (Mon) by corbet (editor, #1) [Link]

...perhaps the lifetime of this particular thread has run its course. I don't see anything good coming from this kind of discussion; let's stop here.

Thank you.

Ownership and lifetimes

Posted Jul 12, 2021 18:52 UTC (Mon) by JanC_ (guest, #34940) [Link] (9 responses)

The reason why signed overflow results are “undefined behavior” in C is that it was implemented differently in various hardware, and the C language didn’t want to force compiler makers to have to introduce slow workarounds on any of that hardware.

Ownership and lifetimes

Posted Jul 13, 2021 10:34 UTC (Tue) by khim (subscriber, #9252) [Link] (8 responses)

This was logical back 1989. Today only two's complement representation is supported and all relevant CPUs handle overflow just fine.

And if compiler developers worry about benchmarks then they can always use an appropriate switch to restore obsolete behavior.

The problem with C/C++ is not certain peculiarities with misunderstandings about some complex corner-cases (it's inevitable when complex systems are involved) but absolute conviction of compiler developers in the fact that “mere users” don't even deserve a discussion.

“It's my way or the highway” attitude permeates the discussion. Well… Rust looks like a nice place on the highway, so maybe it's time to pick 2nd option.

Ownership and lifetimes

Posted Jul 13, 2021 20:51 UTC (Tue) by mrugiero (guest, #153040) [Link] (7 responses)

No, it was not 100% logical in 1989 and it is not 100% logical in 2021. C doesn't just have defined and undefined as categories for behavior. There's also implementation defined. That was really the best choice in this case, I think. This is because implementation defined still means the platform+compiler must make a promise about it. What the promise is is up to them, but they have to be consistent about it. They can also have some extra guarantees, just like `int` size and representation is implementation defined but is guaranteed to be able to represent the range [-2^31,2^31). So, if I know I'm building for ARM64 on Linux with GCC I could have *some* guarantees about integer overflow, even if they are not the same as MSVC on x86 for Windows. This would suffice to avoid these slow workarounds, while not making programmers bend backwards to avoid it altogether.

Ownership and lifetimes

Posted Jul 14, 2021 9:24 UTC (Wed) by khim (subscriber, #9252) [Link] (6 responses)

If I understand correctly they wanted to support systems where signed overflow causes a trap without introducing a ways to intercept that trap. But declaring it “undefined behavior” they achieved that goal: system which don't cause overflow have no need to intercept that trap while systems which cause it are not valid C.

Not sure how much sense that did even back in 1989, but in 2020 it doesn't make any sense at all: there are few CPUs which may still generate signals on overflow, but I don't know any where that's not optional.

And now, when two's complement is mandatory, workaround is, actually, “very simple”. Instead of if (x + 100 < x) you just have to write something like the following: if (std::std::is_signed_v<decltype(x)> ? static_cast<decltype(x)>(static_cast<std::make_unsigned_t<decltype(x)>>)(x) + 100U) < x : x + 100U < x)

And yes, of course you stuff that into a simple templated function. Except since there are no standard modules, no central repo and so on… everyone who wants/needs it would probably need to implement it separately. Which would, most likely, lead to bugs like an uncaught attempts to use that function to check overflow of float or double… or it would be miscomplied because someone used -std=c++20 instead of proper -std=gnu++20… as one of members of pilot Rust program for Android said: I don't feel as satisfied when reviewing Rust code: I always find something to comment on in C++ changes, usually some kind of C++ quirk or a memory management problem. When reviewing Rust changes it makes me uncomfortable if I don't find anything to comment on ("Am I being thorough?").

And these guys are talking about artificial puzzles the language imposes on top of each programming task? Really?

Ownership and lifetimes

Posted Jul 15, 2021 2:16 UTC (Thu) by mrugiero (guest, #153040) [Link] (5 responses)

I find your post very educational, however, it doesn't address my point: all the problems in making signed integer overflow defined behavior could have been fixed by making it implementation defined rather than undefined. Implementation defined means it's still hard to write *portable* code, but that when you do know where it'll run, you know there exists one behavior that is appropriate. That is, if it's implementation defined the compiler you use needs to define a given behavior for the platform it'll run and never violate it. So, gcc would be mandated to tell you what value will result from overflowing, even if such a value is one for SPARC and a different one for x86. I fail to see why it UB made more sense to them than that at the time.

But yeah, talking about artificial puzzles when you have all those kinds of behavior and you don't even mandate warnings when you hit them is a bit hypocritical.

Ownership and lifetimes

Posted Jul 15, 2021 14:12 UTC (Thu) by mathstuf (subscriber, #69389) [Link] (3 responses)

> That is, if it's implementation defined the compiler you use needs to define a given behavior for the platform it'll run and never violate it. So, gcc would be mandated to tell you what value will result from overflowing, even if such a value is one for SPARC and a different one for x86. I fail to see why it UB made more sense to them than that at the time.

I may be misremembering, but didn't some platforms not provide a way to tell? That would require the compiler to emit manual checks for every possible overflowing operation in order to guarantee *a* behavior. This would also mean that optimizing to, e.g., FMA instructions is basically impossible because if the intermediate ops overflow, that needs to be handled as well.

But if there are no platforms that lack an "overflow happened" flag…meh.

Ownership and lifetimes

Posted Jul 15, 2021 15:56 UTC (Thu) by matthias (subscriber, #94967) [Link]

Even if there is a platform that lacks an overflow happens flag. Then on this platform the implementation defined behavior is overflow with the semantics of what overflow means on this platform. Implementation defined means you can choose different semantics for very combination of compiler/platform.

If you do not know which platform your code will run on, you can still be not sure what the result of x+100 is in case of overflow, but you can at least be sure what (x+100)&0 is. And you can be sure that if you do an assertion x+100>x to test for an overflow that the assertion is not optimized away. Ok, it can be optimized away of the compiler can prove that there is not overflow, but this is no problem.

Ownership and lifetimes

Posted Jul 15, 2021 16:26 UTC (Thu) by farnz (subscriber, #17727) [Link] (1 responses)

If such platforms exist and need to be cared about (like you, I'm not sure if they did, or did not), an easy solution would be to make the behaviour of signed integer overflow unspecified, rather than undefined.

In the C language spec, there are four groups of behaviour (from strongest definition to weakest):

  1. Defined. To be a confirming implementation, you must behave in the way the Standard says you should, and there are no choices here. For example, the behaviour of the free(void * ptr) standard library function in C is defined if ptr is NULL or a pointer returned by malloc.
  2. Implementation defined. The Standard sets out restrictions on what this can be defined as, but the implementation gets to choose one concrete behaviour and stick to it. For example, the number of bits in int is implementation defined in C - any value greater than or equal to 16 is permissible, and the implementation must document which value it has cchosen.
  3. Unspecified. The Standard sets out restrictions on what behaviour is acceptable (e.g. "abc" == "abc" must be either 1 for true or 0 for false depending on whether the compiler merges identical string literals, but it cannot be 42 under any circumstances). The compiler can choose any behaviour it likes from the set the standard allows, and can choose different behaviour every time it sees the unspecified construct (e.g. printf("%d %d\n", "abc" == "abc", "abc" == "abc"); can print "0 0", "0 1", "1 0", or "1 1", and can print a different one each time the program is run); it does not have to stick to one choice, as it does for implementation-defined behaviour.
  4. Undefined. If a program run would follow a path with undefined behaviour, the meaning of the program as a whole is not set out by the Standard; it can do anything the compiler implementer wishes, even if the user of the compiler thinks that's a crazy stupid outcome.

If signed integer overflow became unspecified, such that the result of a signed integer overflow could be any integer value, then we're in a much better place. The compiler can just use the machine instruction (assuming it doesn't trap), and we don't have the pain where the compiler goes "well, if this is signed overflow, then behaviour is undefined, ergo I can assume it's not, ergo I can optimise out a check"; instead, signed overflow has to produce an integer, but which integer is not known by the code author.

Ownership and lifetimes

Posted Jul 15, 2021 16:42 UTC (Thu) by khim (subscriber, #9252) [Link]

This is all well and good, but this requires some form of consensus.

And in C/C++ world discussions are just not happening. Why have the coveted “pointer provenance” proposals were not incorporated into standard in 15 years (and counting)?

Because not even C/C++ compiler writers can agree with each other. -std=friendly-c proposal had the exact some fate.

And when someone tries to resolve the issue by “starting from scratch”… the end result is D, Java, C# or Rust… never a “friendly C”…

Rust is just the first such “new C” language which is low-level enough to actually be usable for all kinds of usages. Starting from lowest-level just above assembler.

Ownership and lifetimes

Posted Jul 20, 2021 10:25 UTC (Tue) by anton (subscriber, #25547) [Link]

Concerning integer overflow, the result with a given bit-width is the same for twos-complement arithmetic across all hardware (they all can perform modulo arithmetic, aka wrapping on overflow). And all architectures introduced since 1970 use twos-complement arithmetic exclusively (while, e.g., . So for integer overflow, the behaviour could be just standarized, no need for cop-outs like implementation-defined or implementation-specified. As an example modulo arithmetic has been standardized in Java from the start.

Ownership and lifetimes

Posted Jul 20, 2021 9:58 UTC (Tue) by anton (subscriber, #25547) [Link]

Backwards-compatible C compilers in the relaxed Linus Torvals sense (if no one notices, it's not breakage) are possible and desirable.

Ownership and lifetimes

Posted Jul 13, 2021 16:15 UTC (Tue) by plugwash (subscriber, #29694) [Link] (9 responses)

> Worse, programs are entitled in C++ to just extract the raw pointer from all these smart pointers except weak_ptr (where for obvious reasons there might sometimes not be a raw pointer) and they do.

To actually use the object owned by a C++ smart pointer, you are pretty much forced to explicitly (e.g. "get") or implicitly (e.g. operator* or operator->) extract a raw pointer or reference.

Which ties into your point about lifetimes, once that raw pointer or reference is extracted (again either explicitly or implicitly) there is nothing to ensure that the smart pointer outlives the raw pointer or reference that was extracted from it.

Ownership and lifetimes

Posted Jul 13, 2021 20:56 UTC (Tue) by mrugiero (guest, #153040) [Link] (8 responses)

Is that true? The `->` and `*` operators won't allow creating a copy of the pointer into an lvalue. The object is guaranteed to live until the end of the scope where those operators were called or the next assignment to it, which necessarily happens after the next sequence point, and since you don't get a copy of the pointer to operate with, the lifetime of the referenced object is guaranteed to be correct here. Explicit access is a problem, but I can't think of any scenario where the operators are.

Ownership and lifetimes

Posted Jul 13, 2021 23:16 UTC (Tue) by roc (subscriber, #30627) [Link] (7 responses)

unique_ptr<int> p = make_unique(3);
int& pp = *p;
p = nullptr;
cout << pp;

Ownership and lifetimes

Posted Jul 13, 2021 23:18 UTC (Tue) by roc (subscriber, #30627) [Link] (5 responses)

Yes, this example is contrived and "obvious", but you can achieve similar results in more interesting ways using function calls and temporaries.

Ownership and lifetimes

Posted Jul 14, 2021 9:32 UTC (Wed) by khim (subscriber, #9252) [Link] (4 responses)

You forgot to add std::move to the mix. The fact that after calling foo(std::move(p)); you can have so many possibilities (p may be destroyed or not destroyed, usable or unusable, etc… and compiler wouldn't ever reveal to you what happened… static analyzers sometimes might) opens up another nice room with so many nice footguns of different sizes…

Ownership and lifetimes

Posted Jul 14, 2021 12:06 UTC (Wed) by mathstuf (subscriber, #69389) [Link] (3 responses)

In that example, `p` cannot be destroyed. The destructor is still expected to be called at the end of `p`'s scope, so it must not be destroyed. Instead, it has to turn itself into some empty husk of an object that is safe to:

- destroy
- copy
- assign into (via move or copy)
- call methods on (though many APIs suck and don't provide ways of saying "this makes no sense anymore")

Rust can end `p`'s lifetime at the `foo` call, but C++ has lifetimes go to the end of the scope no matter what because there's no mechanism to say "`p` no longer exists and is now `foo`'s problem".

Ownership and lifetimes

Posted Jul 14, 2021 12:44 UTC (Wed) by khim (subscriber, #9252) [Link] (2 responses)

> In that example, `p` cannot be destroyed.

True, sorry I wasn't clear. Object owned by p may be destroyed or not destroyed, p may be usable or unusable, etc (I hope `p` just means p in your message and not some new footgun C++ gained while I wasn't looking).

> Rust can end `p`'s lifetime at the `foo` call, but C++ has lifetimes go to the end of the scope no matter what because there's no mechanism to say "`p` no longer exists and is now `foo`'s problem".

It's actually even worse than that: p have a destructor (and said destructor is, of course, the whole reason std::unique_ptr exists) thus it's non-trivial for the purposes of call which, in turn, means, that destruction of the object pointed by p still happens in the caller, not callee and, maybe even more importantly, std::unique_ptr can not be passed in register.

Clang have attribute which solves that issue, but libstdc++ doesn't use it (and I'm not even sure if libc++ actually uses that attribute already).

That's why I know some companies which demand that std::unique_ptr shouldn't be used to pass arguments to function and return results from it. Instead you have to use owner<T> syntax and turn it into std::unique_ptr in your function and back when you call anything.

As I have said: “modern C++” is just a huge collection of footguns of different sizes and shapes thus it sounds really funny when it's proponent start talking about artificial puzzles the language imposes on top of each programming task.

At least these rules exist in some kind of central location and compiler actually verifies them! Much better than when their violation turns your program into pile of goo without any complains from the compiler.

Ownership and lifetimes

Posted Jul 14, 2021 13:04 UTC (Wed) by mathstuf (subscriber, #69389) [Link] (1 responses)

> (I hope `p` just means p in your message and not some new footgun C++ gained while I wasn't looking)

I tend to just use "Plain text" here because HTML is a pain to write by hand and seem to wish that Markdown of some kind worked :) . That said, I cannot guarantee that it isn't a footgun you weren't aware of ;) .

> std::unique_ptr can not be passed in register.

True. There's discussion about breaking the ABI for this (and other things), but ABI breaking is a *huge* topic and you have the monorepo-using "we rebuild everything ourselves so what even is ABI stability" on one side and the "we have critical libraries we cannot recompile anymore because we lost the source 10 years ago" on the other. I have no idea if that discussion will go anywhere other than heat generation, but I hope something *useful* comes out of it at least even if it is just "let's design in some escape hatches for ourselves in the future".

Ownership and lifetimes

Posted Jul 17, 2021 16:06 UTC (Sat) by mrugiero (guest, #153040) [Link]

Then you use an older system I guess. If you were fine with no updating your software you're probably fine with that too.

Ownership and lifetimes

Posted Jul 15, 2021 2:10 UTC (Thu) by mrugiero (guest, #153040) [Link]

Stupidly enough, I didn't think of references. It shows how much time passed since the last time I wrote C++ :)

Ownership and lifetimes

Posted Jul 14, 2021 7:28 UTC (Wed) by ncm (guest, #165) [Link] (14 responses)

You reveal perhaps more than you intended.

Stroustrup never, ever rants. When he writes, every word counts. To perceive ranting only demonstrates confusion.

Your confusion about the One Definition Rule is revealing. In fact, the only requirement to spend an entire career coding C++ never once troubled by violations of the ODR is simply to obtain declarations via headers, rather than copy-pasting them. C has always been subject to undetected failures resulting from identical malpractice; what is new is that C++ gives the rule a formal name. C's linkage model, inherited by C++ for backward compatibility, brought with it C's weaknesses, but C++ now provides module support..

To believe that object ownership and lifetimes have not always been understood and actively managed as a matter of course by all C++ (and C) coders demonstrates further confusion.

To believe that C++ is not rapidly growing and changing is to believe a fantasy that C++20 is hardly different from C++17, from C++14, from C++11, from C++03, from C++98. In fact no language defined by a Standard has evolved more over that time, and few of the rest have. As more people adopt C++ anew in any given week than have ever heard of Rust, there is no worry about lunch. "There are only two kinds of languages: the ones people complain about, and the ones nobody uses." When we see more complaints about Rust than marveling over its ability to do what has been done before, it will merit more attention.

Writing fantastic falsehoods about a language and its users reveals nothing about them, but much about the writer.

Ownership and lifetimes

Posted Jul 14, 2021 8:25 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link] (1 responses)

> In fact, the only requirement to spend an entire career coding C++ never once troubled by violations of the ODR is simply to obtain declarations via headers, rather than copy-pasting them.

That's incorrect. You can get ODR violations by having multiple clashing template specializations, that can be in different parts of the application (for the added fun).

Back in MSVS 2006 days, I also spent several days debugging an issue where two inline functions (exempt from ODR) had different padding due to different compilation options.

Ownership and lifetimes

Posted Jul 14, 2021 9:40 UTC (Wed) by khim (subscriber, #9252) [Link]

> You can get ODR violations by having multiple clashing template specializations, that can be in different parts of the application (for the added fun).

No need for even that. Compile your header once with -mavx512f and once without it and you may have nice and long investigation where you would try to understand why minor changes to totally unrelated code creates something that explodes on Ryzen cluster and where clean build either always produces working binaries but incremental builds sometimes lead to crashes.

Have fun.

> Back in MSVS 2006 days, I also spent several days debugging an issue where two inline functions (exempt from ODR) had different padding due to different compilation options.

This story just never dies.

Ownership and lifetimes

Posted Jul 14, 2021 8:33 UTC (Wed) by Cyberax (✭ supporter ✭, #52523) [Link] (3 responses)

> To believe that C++ is not rapidly growing and changing is to believe a fantasy that C++20 is hardly different from C++17

Honestly, C++ is not growing rapidly. C++20 improvements were mostly in libraries and infrastructure around them.

The major language-level feature in C++20 are concepts, but they had to be cut down quite a bit. Coroutine support is at the barest minimum possible. Modules haven't made it either.

Ownership and lifetimes

Posted Jul 15, 2021 5:52 UTC (Thu) by ncm (guest, #165) [Link] (2 responses)

You have failed to keep up with events, so you embarrass yourself by publishing falsehoods.

Ownership and lifetimes

Posted Jul 15, 2021 13:43 UTC (Thu) by mathstuf (subscriber, #69389) [Link]

These snipe-y comments sound awfully embarrassing by themselves to me too. If the post is so embarrassing, why not just leave them to stand on their own if you're not going to contribute anything to the discussion in your reply?

Ownership and lifetimes

Posted Jul 18, 2021 13:13 UTC (Sun) by flussence (guest, #85566) [Link]

Congratulations on becoming the new IBM OpenOffice Guy.

Ownership and lifetimes

Posted Jul 14, 2021 9:28 UTC (Wed) by jezuch (subscriber, #52988) [Link] (1 responses)

> In fact, the only requirement to spend an entire career coding C++ never once troubled by violations of the ODR is simply to obtain declarations via headers, rather than copy-pasting them.

Newer compilers actually warn about ODR violations. Previously is was completely silent, so nobody knew how much they were violating it. I once compiled some random C++ program with such a compiler and I just sat and watched as the ODR violation warnings just scrolled by, on and on, endlessly.

So. "The difference between practice and theory is bigger in practice than in theory." (I think it's an old engineering saying.)

Ownership and lifetimes

Posted Jul 14, 2021 12:26 UTC (Wed) by tialaramex (subscriber, #21167) [Link]

As with the Java ConcurrentModificationException, these are Best Efforts Only. You might get warnings, but you might not. There are no false positives for these diagnostics (if you get the diagnostic, you do have the problem) but there are false negatives, and you can't know how many.

Ownership and lifetimes

Posted Jul 14, 2021 15:20 UTC (Wed) by tialaramex (subscriber, #21167) [Link]

Another, but longer-lived, surprise for me has been how many people are bad at debugging because they can't let go of a Truth they believe in, despite it being contradicted by Facts now made evident to them. I don't know how people who can't get this right ever debug anything, it must be very difficult.

So, the Fact is that Stroustrup doesn't talk about ownership in that book. The much later (less than ten years old as I write this) Fourth Edition does talk extensively about ownership. Can't shut up about it actually. In some places, nearby text survives, or has been transplanted from earlier editions, but the talk of ownership is new. Why? Because of shared_ptr and unique_ptr which did not exist when the Second Edition was written. This inspires Stroustrup to explain even before introducing any C++ syntax at all, that programmers must "represent ... Ownership relationships" in their code. Which, in C++ 11 they now can try to do.

Throughout the Fourth Edition's "Tour" of C++, there are opportunities, taken or at least pointed out to the reader for their own benefit, to express Ownership. The Desk Calculator example explicitly tracks whether it owns the Input Stream it is using, offering methods to give it a reference (which it doesn't own) or a raw pointer (which it does) and clean up appropriately. Second Edition makes no mention of this, it too has a Desk Calculator example, but it doesn't bother with Input Streams, it just reaches inside the system's standard input and mangles that directly.

So, you're wrong, factually even if you firmly believe now that ownership has "always been understood and actively managed" it seems to have escaped mention by the language's father in their text intended to introduce the language _until_ decades later the language grew facilities to actually try to "actively manage" this problem.

Lifetimes are an interesting contrast. The Second Edition does spend a few paragraphs on this topic, but mostly the reader is encouraged not to think about it too hard. Objects begin living when they're Constructed, and this lifetime ends when their name goes out of scope whereupon they will be Destroyed. Except, that's not quite true and there are a few paragraphs trying to justify that before a reference to paragraphs later in the book about the Free Store.

By the Fourth Edition, lifetimes get their own sizeable Chapter in the much larger book, "Construction, Cleanup, Copy and Move". So I agree that by this point Stroustrup is aware that this is a problem, indeed that chapter uses words like "trap" and "mistake" and most sub-sections are accompanied by considerable verbiage basically encouraging the programmer to try harder not to get this wrong. But he doesn't have anything to really propose as a solution. Yet.

Today, Stroustrup has a proposed change to C++ to add actual lifetime tracking. I don't expect it will make it into C++ 23. You claim that Stroustrup "never, ever rants" but he seems quite animated when it comes to the committee not accepting all his ideas as quickly as he might like or adjusting them in ways he isn't pleased with, and equally animated about cutting down other people's proposals.

Ownership and lifetimes

Posted Jul 14, 2021 15:37 UTC (Wed) by mathstuf (subscriber, #69389) [Link] (2 responses)

> In fact, the only requirement to spend an entire career coding C++ never once troubled by violations of the ODR is simply to obtain declarations via headers, rather than copy-pasting them.

Just had to deal with this last week in fact. ODR violations are rampant when some dependency does not follow C++ "best practices" nor uses extra-language bits that are compiler/platform dependent (`__attribute__(visibility)` and `__declspec`).

One of our dependencies doesn't export symbols properly. Luckily, CMake has a way to say "le sigh, just export everything" on Windows to fix that problem. However, there's a deeper problem lurking in such a codebase: RTTI duplication. There are these simple interface classes that have their definitions completely in the header. Seems reasonable; header-only is fine, right? However, the source code of the library does `typeid` comparisons with a `std::type_info` that is passed in to see which of these interface types you're asking about. Because the complete definition (trivial `{}` bodies for the ctor and dtor with pure virtual methods otherwise) are in the header, every library that sees this declaration can say "huh, no export symbol? I can make it mine" and generate its own unique RTTI table for the type. The linker will resolve these together into a shared library boundary, but nothing resolves it across a shared library boundary at runtime, so you then get two different `typeid` blocks for the same type. Hilarity ensues when the dependency goes "yeah, I have no idea what you're on about, have a `nullptr`".

Now, C++ *could* have just standardized `export` or some other syntax to say whether a declaration "belongs" to the current TU's target or not, but that requires macros at compile time to know which library you're actually compiling for so things get linked up properly. And C++ *hates* talking about compilation strategies beyond "pile of TU outputs linked into a program" because it never bothered to reign in the madness from the beginning and now suggesting anything is likely to get somebody clutching pearls immediately. So, C++20 has modules where what is "in" and what is "out" can be more easily tracked with actual standard-compliant syntax, but this has been an issue since C's textual inclusion compilation model was a thing and only now we have a solution that requires rewriting all of your headers and leaving old, dumb `%.o: %.cpp` build systems behind anyways (because compiling C++20 modules is the same problem as compiling Fortran modules and everyone remembers how much fun that has been, right?).

Ownership and lifetimes

Posted Jul 15, 2021 11:49 UTC (Thu) by foom (subscriber, #14868) [Link] (1 responses)

The problem you describe here is that windows (PE/COFF) DLLs don't support C++'s vague linkage symbols across DLLs properly. This makes windows DLLs effectively incompatible with the C++ standard's requirements.

Neither MacOS (mach-o) nor Linux (elf) shared libraries have this issue. They both work fine in this situation, by ensuring that vague linkage symbols are exported and deduplicated across the shared library boundaries by default.

One might expect this would have been addressed in Windows at some point in the last few decades...but it hasn't been.

Ownership and lifetimes

Posted Jul 15, 2021 13:46 UTC (Thu) by mathstuf (subscriber, #69389) [Link]

Well, the fun thing here is that the problem appeared on macOS arm64. Previously, everything had been working on all platforms (our library does use `-fvisibility=hidden` to emulate Windows behavior on the other platforms because we *do* export our symbols properly). Which makes me wonder why the typeinfo wasn't already duplicated…but whatever. Anyways, something is different with macOS arm64 and this problem came up and now it's fixed by improving the code, so I'm happy at least (and it's one more "ghostly linker fun" bug under my belt :/ ).

Ownership and lifetimes

Posted Jul 18, 2021 18:10 UTC (Sun) by marcH (subscriber, #57642) [Link]

> To believe that C++ is not rapidly growing and changing is to believe a fantasy that C++20 is hardly different from C++17, from C++14, from C++11, from C++03, from C++98.

OK, so language theory discussions are fascinating (thank you everyone) but now let's get real a bit. Let's pretend I'm managing a software team and I've been asked to get rid of most safety issues in the C++ codebase of our mission-critical application. What -Wold-unsafe-c++ compiler flag do I turn on to make sure my not-all-perfect developers don't make any mistake and stick to "modern C++"? If that compiler flag is not available, what static analyzer(s) should I use? I have practically unlimited budget so I don't mind commercial products. (Even an unlimited budget is unfortunately not enough to hire an elite team of C++ developers who never make any mistake)

> there is no worry about lunch.

I don't think anyone doubts C++ will still make a lot of money after even the youngest people in this discussion are dead, no reason to feel threatened.
There is a shortage of developers, not a shortage of code to write. Apparently you can still get paid to fix COBOL code:
https://www.theverge.com/2020/4/14/21219561/coronavirus-p...

Ownership and lifetimes

Posted Jul 18, 2021 18:49 UTC (Sun) by marcH (subscriber, #57642) [Link]

> Stroustrup never, ever rants. When he writes, every word counts. To perceive ranting only demonstrates confusion.

Whether it's true or not, I'm afraid you don't realize how cultish that is perceived.


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds