The abhorrent š„ rites of C
-
Those classes are in the standard library.
Where is the standard library class to callEnable()
on a class that the standard library knows nothing about, in a hierarchy that the standard library knows nothing about? This is going from "wrong" territory to "outright lie" territory very quickly.
-
This is going to be a C++17 feature, as I said. Any other questions?
(Boost has something like that already and you can also usually just copy&paste the code from the standards proposal.)
-
This is going to be a C++17 feature, as I said. Any other questions?
No, what's going to be a C++17 feature is something whose use in code is slightly more complicated than an explicittry/finally
, and whose implementation is significantly worse. (And it requires 18 pages to describe it, apparently.try/finally
can be described, perfectly comprehensibly, in a paragraph or two.)
-
Yeah, because you remember you can't do it on a FILE.
Stop trying to use stdio as an example of good code. You're just embarrassing yourself. The only good thing about it are
sprintf
andsscanf
, and those two functions are very sharp tools (i.e., excellent in the hands of people who know how to use them, and hazardous in the hands of ignorant idiots).
-
try/finally can be described, perfectly comprehensibly, in a paragraph or two
Description plus all the standardese plus a sample implementation in two paragraphs?
Also, that 18-page document describes more than just scope_exit().
-
Yeah. You need ugly hacks like RAII in C++ because the language is broken and it doesn't have a proper try/finally construct. Which is interesting because RAII, in its entirety, is built upon a try/finally construct, but it's not exposed to the C++ developer, which means that other uses of try/finally that don't involve freeing memory turn into ugly workarounds. It's the very definition of "abstraction inversion."
finally is not needed in C++ is what you'd probably heard already. Well, it's true, but it does leave a hole when something, like your example, is needed. That's where helper like boost scope exit come into place. C++ could have both, I guess.
But the point about RAII is off. It's more logical that an object which owns resources should be responsible for releasing them. try/finally way is moving that responsibility to the owner of the owning object. It was designed because you don't have destructors in languages which use that construct and that's all. And you can't have destructors when your object lifetime is not clearly defined, but depends on whatever GC feel right now.
-
And it requires 18 pages to describe it
BTW: A simple implementation is just a few lines long:
-
Description plus all the standardese plus a sample implementation in two paragraphs?
It takes a bit more than that. [spoiler](I can't be bothered to look up the equivalent for C#; it'll be a bit shorter because it uses a different keyword for some of the semantics. The overall length of the two will probably be not hugely different; the semantics of the two languages are quite comparable at this level, despite what some fanbois claim.)[/spoiler]
-
Exactly. He's comparing apples (simple description) and oranges (language standard proposal).
-
Now - do I need to use delete or delete[] on this pointer...?
When you have non-owning raw pointer, which should be the norm as opposed to C, you never call delete. Your problem is solved by not creating it in the first place.
-
But the point about RAII is off. It's more logical that an object which owns resources should be responsible for releasing them. try/finally way is moving that responsibility to the owner of the owning object.
Wait, what?It was designed because you don't have destructors in languages which use that construct and that's all.
Huh?And you can't have destructors when your object lifetime is not clearly defined, but depends on whatever GC feel right now.
Umm... where in the world did you get the idea thattry/finally
is exclusively a managed code thing? (And again,try/finally
is not about destructors, memory management, or resource management. Get that idea out of your head. It's completely wrong, and as long as you keep thinking about it that way you'll never understand it. That's only one of many things it can be used for.)
-
Please name 5 use cases for finally that do not involve resource management and are made unnecessarily complicated by the fact that C++ favors RAII.
I don't use C++ a lot, but one example comes readily to mind:
- You can't use RAII when you need a variable of an abstract/partial class.
You'll run into this any time you need to determine which class is the correct implementation of an abstract class at runtime.
-
You can't use RAII when you need a variable of an abstract/partial class.
I'm not sure I follow. Care to elaborate?
-
Umm... where in the world did you get the idea that try/finally is exclusively a managed code thing?
Technically, it's a semantics thing: the
finally
clause executes when the body of thetry
terminates (and after anycatch
clauses have been processed) whether the overall result of the evaluation is success, abreak
/continue
signalling (a different sort of success), areturn
(yet another kind of success) or an exception. If thefinally
clause does not throw an exception, the result of the overall construct is the same as if thefinally
clause was not there. It's strongly recommended thatfinally
clauses be kept simple (so nothrow
,break
,continue
orreturn
) because while not keeping them simple is well-defined, the resulting program is rather difficult to debug.And it looks like I have spaces in my post. Thanks, @end!
-
With all these perfect solutions to avoid the problems, would someone like to tell me why malloc(), new and new[] are still things in C+11, since they got rid of other things (like the old meaning of auto)?
Any answers that involve keeping them there will be directed back to my original question.
Why do you claim they're still a thing? Today nobody uses new except in-place construction. Nobody uses delete, because you have smart pointer for this. malloc() or equivalent is only used deep down in allocators, which probably 99% of programmers will never touch.
-
That makes even less sense. Why in the world would you destroy your mutex while it's still in use?
You're right, that's why it's considered an error and undefined behavior.
-
You're right, that's why it's considered an error and undefined behavior.
Yeah, sorry about that. Terminology confusion. Someone said "owner" in a conversation about resource disposal, and I thought they meant "owner" in the memory management/resource disposal sense, when they really meant "lock holder."
-
It's still stupid to create an entire class to do what you should be able to do in one line of code without having to create a class for it. (And even stupider to create a template to create that class!)
std::unique_lock lock{mutex}; // one line
As for the template - there many kinds of mutexes, you know (or not). You can even write your own thing, which is lockable and use a unique_lock on it. Note it isn't named unique_mutex_lock.
-
One line that instantiates an object from a class that had to be created because the language is so broken that it's incapable of doing this without a class!
-
You need ugly hacks like RAII in C++ because the language is broken and it doesn't have a proper try/finally construct.
One of the better code-structure inventions of recent years has been the using/try-with-resources of C# and Java.
The problem with that is they're not transitive, while RAII is (i.e. destruction of an object will always trigger a destruction of all of its members). And it removes the need for designing a disposed state for a type. Which can significantly reduce mistakes ("someone added a disposable thing to this disposable thing but forgot to update Dispose" etc). You can get around that (IoC or generating code or whatever) but RAII gives you that for free, and for all types.
You might not see anything wrong with manually maintained disposing code (it's certainly doesn't bother me most of the time due to not writing anything state-heavy), but calling RAII a hack is dumb. It's automation of correctness.
try/finally is not about resource management.
For vast majority of cases it is. For others it's just syntactic sugar.
Then why are memory leaks so common in released software written in C++? Must not be as magically simple as you claim.
Because people think like you and not use RAII.
The only good thing about it are sprintf and sscanf
Haha, no.
-
Oh, right! C++'s object model is utterly broken. It has objects-as-value-types, which should never exist in the first place. But that's a completely different discussion.
Goddamn, that's even a more absurd statement than what that C guy writes.
-
@powerlord said:
You can't use RAII when you need a variable of an abstract/partial class.
I'm not sure I follow. Care to elaborate?
Say I have a class named BuiltinVoteStyle_Base that has 9 methods defined in its header file, but only implements 4 of those methods in its .cpp file. The other 5 are declared as
virtual
.I then have 3 classes that extend that class that each implement the 5 missing methods.
You can't declare
BuiltinVoteStyle_Base voteStyle;
because that's a compiler error. You must declare it as some sort of pointer and set it to an instance of the correct class at runtime.
I've actually run into this writing an addon for Valve game servers, with different implementations for Left 4 Dead, Left 4 Dead 2, Team Fortress 2, and Counter-Strike: Global Offensive1.
1I didn't actually do the CS:GO one before I realized certain operations were just easier to do from the SourceMod scripting host instead of directly through C++
Filed under: I could have just said "Polymorpism", This particular code had to compile using the Visual Studio 2008 compiler and GCC 3.x so no std::unique_ptr
-
One line that instantiates an object from a class that had to be created because the language is so broken that it's incapable of doing this without a class!
If, at this point, you still don't get the benefits of using an explicit object for the resource, after all the examples and explanations, then there's no point in arguing with you:
I was also talking about this case:
std::unique_lock<std::mutex> foo() {
// lock some mutex
return lock;
}std::unique_lock<std::mutex> bar() {
// do stuff
return foo();
}int main() {
auto lock = bar();
// do stuff
return 0;
}Really easy to handle with RAII, extremely unintuitive and error-prone if you don't have an object that represents the lock you're holding.
-
One line that instantiates an object from a class that had to be created because the language is so broken that it's incapable of doing this without a class!
I thought one of the philosophies of C++ was to be able to implement as code stuff that other languages had to write into the language. I'm sure we could all fight over the desirability of that.
-
Then why are memory leaks so common in released software written in C++? Must not be as magically simple as you claim.
Because not everybody uses smart pointer, while they should. Also dealing with C API can add the burden (but that's another fire I wish to start when this one is done).
Memory leaks are common in C++ software. Memory leaks are common in software written in other native languages, where memory must be managed manually.
And here you strike gold. Just don't do it. Don't be stupid. Don't write C-like code in C++.
-
And how many times can you NOT search why/where C++ outperforms C?
Uhm dude, I'm on your side of the argument here (long-time C programmer, switched to C++/C#, wincing whenever I use C again), but the burden of proof is on you. Someone debating against you isn't going to bother searching for evidence in your favor. Hell, even I won't. That's your job to do the googling, and link (or even better, quote and link) the results.
Such is the nature of asinine Internet forum debates. And probably meatspace debates, as well.
-
You must declare it as some sort of pointer and set it to an instance of the correct class at runtime.
Ok, but how does that relate to RAII?
std::unique_ptr<Derived>
is move-assignable tostd::unique_ptr<Base>
.
-
Stop trying to use stdio as an example of good code. You're just embarrassing yourself.
Whoa, where did you get that idea? stdio good code? How? When?
-
You can't use RAII when you need a variable of an abstract/partial class.
@powerlord said:Say I have a class named BuiltinVoteStyle_Base that has 9 methods defined in its header file, but only implements 4 of those methods in its .cpp file. The other 5 are declared as
virtual
.I then have 3 classes that extend that class that each implement the 5 missing methods.
You can't declare
BuiltinVoteStyle_Base voteStyle;
because that's a compiler error. You must declare it as some sort of pointer and set it to an instance of the correct class at runtime.
But you can use an
unique_ptr<BuiltinVoteStyle_Base>
.
-
Wait, what?
What what?
You either have an object which owns a resource (let's say a stream) and that objects releases that resource when it gets destroyed, or you have that object again, but YOU have to remember to explicitly call some close() function for the object to release it's underlying resources. That's a pretty simple example, IMHO.
Huh?
Care to write full sentences?Umm... where in the world did you get the idea that try/finally is exclusively a managed code thing?
Which languages invented them?
-
Yeah, sorry about that. Terminology confusion. Someone said "owner" in a conversation about resource disposal, and I thought they meant "owner" in the memory management/resource disposal sense, when they really meant "lock holder."
Technically it's called a lock owner, not a holder, hence the misunderstanding.
-
One line that instantiates an object from a class that had to be created because the language is so broken that it's incapable of doing this without a class!
That's a pretty moot argument. I can also say the opposite: try/finally was created because the language is so broken that it cannot have deterministic destructors.
-
To get the same kind of optimization opportunities, the C programmer would have to write a specialized sorting function for each type, repeating the same code over and over with just a change in the comparator called.
Bullshit.Template instantiation creates specialised code at compile time, which is great, but it's not an optimisation in itself, nor does it allow for additional specialisation.
A C coder might well do much the same thing (without the type safety benefits) using a macro, or simply by using aggressive inlining (which specialises code at the call site, keeping all the type safety benefits).
There is nothing in this example, optimisation-wise, which C++ can do that can't also be done in C, given a good enough compiler. C++ does provide a lot of optimisation hints to the compiler that C doesn't, but on the other hand it's so hideously fucking complex compared to C it's much easier to write horribly inefficient code using it.
All of which is not to say that C is a wonderful language. It's not. It's a fucking horrible blot on the world of IT, arguably far more damaging than even PHP. It's just that C++ is more of it. Complication piled on complication piled on Rube Goldberg device, but it still somehow needs Boost to make it usable. It doesn't help that nobody knows what version of C++ you're talking about when you say "C++", either. See the hundreds of posts over the last day...
-
Uhm dude, I'm on your side of the argument here (long-time C programmer, switched to C++/C#, wincing whenever I use C again), but the burden of proof is on you.
The fact is I did a quick google and linked a page in a book (and you guys seem to have DoSed Google Books , which explains one of the mechanisms why this is true. On the other hand he either completely ignored it, or didn't understand any of it.
-
You might not see anything wrong with manually maintained disposing code (it's certainly doesn't bother me most of the time due to not writing anything state-heavy), but calling it a hack is dumb. It's automation of correctness.
I have nothing against "the automation of correctness," assuming what's being automated is actually correct. (Which should not be taken for granted!) What I have a problem with is the abstraction inversion.try/finally is not about resource management.
For vast majority of cases it is. For others it's just syntactic sugar.
It's not syntactic sugar, it's a fundamental code flow primitive that becomes necessary as soon as you introduce exception handling to the language, which is why every language that supports exception handling supports it. (Except C++. See above, re: broken.)Goddamn, that's even a more absurd statement than what that C guy writes.
Got anything to back that assertion up with?The sine qua non of objects--what makes them something actually different and useful, something more than simply structs with methods welded on--is polymorphism: inheritance and virtual methods.
The thing is, you can have inheritance, or you can have value types. You can't have both, or you run into all sorts of huge messes when you try to assign or pass objects. (Copy constructors, object slicing, etc.) Value-type semantics utterly ruin inheritance and all the benefits it brings to the table.
If, at this point, you still don't get the benefits of using an explicit object for the resource, after all the examples and explanations, then there's no point in arguing with you:
- You have not explained any benefits. You have shown examples that are more complicated than explicit
try/finally
, and asserted that a benefit exists, but at no point has this benefit been demonstrated, because it does not exist. - There's that word again, "resource." You're still thinking of it in terms of resource management. Are you literally incapable of getting out of that wrong mental model?
What what?
You either have an object which owns a resource (let's say a stream) and that objects releases that resource when it gets destroyed, or you have that object again, but YOU have to remember to explicitly call some close() function for the object to release it's underlying resources. That's a pretty simple example, IMHO.
Sure, but how does that have anything to do with "moving that responsibility [from the owning object] to the owner of the owning object"?Care to write full sentences?
Care to write stuff that actually makes sense?Which languages invented them?
Not sure where it originally came from In The Beginning, but I do know that Pascal had it (and destructors) before Java or C# even existed. This is becausetry/finally
is not about object destruction; it's about exception safety.That's a pretty moot argument. I can also say the opposite: try/finally was created because the language is so broken that it cannot have deterministic destructors.
You can say that, but you would be objectively wrong to say so, because it existed before the concept of managed code and "no deterministic destructors" was invented.
- You have not explained any benefits. You have shown examples that are more complicated than explicit
-
Template instantiation creates specialised code at compile time, which is great, but it's not an optimisation in itself, nor does it allow for additional specialisation.
And that is entirely not true, because the compiler can optimized different specializations differently. Templates are not C macros where you simply get a blob of text pasted into.
-
@tufty said:
Template instantiation creates specialised code at compile time, which is great, but it's not an optimisation in itself, nor does it allow for additional specialisation.
And that is entirely not true, because the compiler can optimized different specializations differently. Templates are not C macros where you simply get a blob of text pasted into.
Of course the compiler can optimise different specialisations differently. It can also optimise different "blobs of pasted text" differently. In both cases you're talking about different pieces of code, which can thus can be optimised differently.
-
Okay, concrete example time. I code in C#. C# is a language that has both a destructors mechanic and a try/finally mechanic (and yes, the former is merely syntactic sugar for the latter).
And you know what? Try/finally is cumbersome. If there's anything I need to do (and undo with finally) several times, I'm going to use an
IDisposable
-implementing object in ausing
block.I have a class for changing the foreground color of the console output and changing it back when disposed. I have a class for swapping any two values and swapping them back when disposed. I even have a class to call a parameterless delegate (=function pointer, for those who don't know .net) (or even a lambda expression) when disposed. All this because a
using
block is less cumbersome than try/finally.That's just how cumbersome try/finally is, compared to RAII mechanics.
-
Got anything to back that assertion up with?
Two words - moving ownership. Follow it up.
The thing is, you can have inheritance, or you can have value types. You can't have both, or you run into all sorts of huge messes when you try to assign or pass objects
That's called slicing in C++ and it's a thing only stupid programmers do. Compilers tend to scream at this, tho.
Sure, but how does that have anything to do with "moving that responsibility [from the owning object] to the owner of the owning object"?
Read that long sentence again and see who initiated resource release.
Care to write stuff that actually makes sense?
Pretty hard when all I get from you is "huh".
This is because try/finally is not about object destruction; it's about exception safety.
Wrong - it's about resource release/cleanup, which needs to be executed always. Much like deterministic destruction of objects.
You can say that, but you would be objectively wrong to say so, because it existed before the concept of managed code and "no deterministic destructors" was invented.
Care to elaborate on that, because I have a feeling you didn't exactly get my point?
-
Of course the compiler can optimise different specialisations differently. It can also optimise different "blobs of pasted text" differently. In both cases you're talking about different pieces of code, which can thus can be optimised differently.
The example from that book shows how templates can be more optimized than macros, if you wish. Templated functor can be inlined, while no macro will save you from indirect C-style function call.
-
the language is so broken that it's incapable of doing this without a class!
Why do you hate classes? Writing classes is the main way you create abstractions in C++. They're used to codify concepts and relationships. When you have a new concept you want to handle, you write a class for it. It's not a failure of the language, is how it's intended to be used.You must declare it as some sort of pointer and set it to an instance of the correct class at runtime.
Well, yeah. What would you want to happen if you instantiate the base class and call one of the undefined virtual methods? The compiler keeping you from making mistakes is a GOOD thing, not an error.The sine qua non of objects--what makes them something actually different and useful, something more than simply structs with methods welded on--is polymorphism: inheritance and virtual methods.
[Citation needed] Just because one language calls something one way, doesn't mean every language has to be limited that way. I get a lot of mileage out of classes without ever writing a single virtual method.The thing is, you can have inheritance, or you can have value types. You can't have both, or you run into all sorts of huge messes when you try to assign or pass objects. (Copy constructors, object slicing, etc.) Value-type semantics utterly ruin inheritance and all the benefits it brings to the table.
According to @Ronin, OOP sucks and C++ sucks because it has too much of it. You complain that C++ isn't committed enough to OOP. Neither of you have a clue about C++.
-
Templated functor can be inlined, while no macro will save you from indirect C-style function call.
A indirect function call can be inlined at its call site as well, as long as it can be proven the function being called will always be the same. In the case we're talking about, the equivalent of a C++ templated algorithm, the indirect function call will always be the same, and can almost certainly be reduced to a direct call, or even an inlining of the function. C optimisation does not stop at "expand templates".
-
A indirect function call can be inlined at its call site as well, as long as it can be proven the function being called will always be the same.
Unfortunately, the compiler cannot check this, but that would be a nice optimization.
-
Complication piled on complication piled on Rube Goldberg device, but it still somehow needs Boost to make it usable.
Eh, not so much these days. But yes, C++ is a terrible language with a shitty ecosystem and using it is a pain. RAII as a concept is not C++-specific, though (Rust has
Drop
trait for that, for example), and holding RAII against C++ is literally insane and a sign ofIt's not syntactic sugar, it's a fundamental code flow primitive that becomes necessary as soon as you introduce exception handling to the language, which is why every language that supports exception handling supports it.
99% of need for always executing given code even when unwinding is external resources. I rarely ever write
finally
in any language (and I've never felt the need to in C++), especially if it hasusing
-like blocks. Because unlike what you think, writing helper types for that is not a problem, and it makes maintenance of call sites less error-prone.
-
Two words - moving ownership. Follow it up.
So... more context-less gibberish from you. GotchaWrong - it's about resource release/cleanup, which needs to be executed always.
it's about arbitrary cleanup which needs to be executed always, and the reason it exists is because it's necessary for correct cleanup when exceptions can be thrown. It has as much to do with object destruction as glass jars have to do with fruit jam: that's one thing that is frequently found inside of them.Care to elaborate on that, because I have a feeling you didn't exactly get my point?
What is there to elaborate on? Pascal hadtry/finally
before Java and C# made OOP with non-deterministic object cleanup a thing. Therefore, it's objectively incorrect to claim that it's something that was created to address a lack of deterministic object cleanup. If that's not the point you're attempting to make, please state your point more clearly, because that's what you said.Why do you hate classes?
Why do people keep asking stupid questions that I already answered even though they were stupid the first time? I don't hate classes; I hate using the wrong tool for the job, particularly when they're more complicated than using the right tool for the job.Writing classes is the main way you create abstractions in C++.
So what you're saying is, when all you have is a , everything starts to look like a class?99% of need for always executing given code even when unwinding is external resources.
You don't do much GUI work, do you?Because unlike what you think, writing helper types for that is not a problem, and it makes maintenance of call sites less error-prone.
...and you don't do much maintenance work, do you? Because if there ever was an inviolable First Law of Maintenance Thermodynamics, it would be this: the more layers of abstraction your implementation is buried under, the harder it is to debug and fix because the harder it is to find out what is actually going on and where it's taking place.
-
You don't do much GUI work, do you?
Feel free to present examples of how fundamental
finally
is in GUI code.
-
it still somehow needs Boost to make it usable
That hasn't been true for years.
You have not explained any benefits
I even showed you example code in the very post you replied to. I'm starting to get the feeling that you are actively ignoring my points.
There's that word again, "resource."
Yes, because my example, whose existence you deny, uses RAII for resource management.
Are you literally incapable of getting out of that wrong mental model?
Maybe I disagree and think it's the correct mental model in most cases. But I also linked to the standards proposal which provides a generic RAII wrapper for non-resources.
Yes, you're actively ignoring my points.
This is because try/finally is not about object destruction; it's about exception safety.
Those two things are not as unrelated as you make it seem.
RAII as a concept is not C++-specific, though (Rust has Drop trait for that, for example), and holding RAII against C++ is literally insane and a sign of
QFFT
-
You don't do much GUI work, do you?
Occasionally, and I use QML for the GUI. Your point being?
-
So... more context-less gibberish from you. Gotcha
No, just something you actually can look up. Or you can just play dumb and evade it, since it would prove you wrong.
it's about arbitrary cleanup which needs to be executed always, and the reason it exists is because it's necessary for correct cleanup when exceptions can be thrown. It has as much to do with object destruction as glass jars have to do with fruit jam: that's one thing that is frequently found inside of them.
So you're disputing my claim of it being used for cleanup by saying that it's being used for cleanup?
What is there to elaborate on? Pascal had try/finally before Java and C# made OOP with non-deterministic object cleanup a thing. Therefore, it's objectively incorrect to claim that it's something that was created to address a lack of deterministic object cleanup. If that's not the point you're attempting to make, please state your point more clearly, because that's what you said.
My point was that you've made a statement which only bases on your assumptions. I showed you how absurd it looks, by making the inverted one.
-
@tufty said:
A indirect function call can be inlined at its call site as well, as long as it can be proven the function being called will always be the same.
Unfortunately, the compiler cannot check this, but that would be a nice optimization.
Again, bullshit. In the case you're talking about,std::sort
, you provide a fixed functor, right? So to compare apples with apples, you'd have to allow me something like
[code]
#define std_sort(begin, end, functor) \
ā¦
[/code]
So when I then use it, thusly
[code]
std_sort (foo, bar, &compare);
[/code]
the expansion at that point will have a known value, and can be inlined. A far less common usage might be
[code]
int_t (*compare_p)(int, int) = &compare;
ā¦
std_sort (foo, bar, compare_p);
[/code]
But again, at the call site,compare_p
has a known, static, value, and can probably be inlined. And so on, including passing in various function pointers, which can be dealt with by rolling the sort algorithm "up" the call stack and specialising for the various types, ā¦Obviously, this requires a smart enough compiler, and one of those probably doesn't exist for C (or, in most cases, for C++). But the idea that those optimisations cannot be made for C is false.