Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411276 Posts in 69323 Topics- by 58380 Members - Latest Member: bob1029

March 28, 2024, 12:28:36 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)General programming discussion
Pages: 1 ... 13 14 [15] 16
Print
Author Topic: General programming discussion  (Read 28923 times)
Crimsontide
Level 5
*****


View Profile
« Reply #280 on: April 29, 2018, 11:08:00 PM »

I'm unsure as to how that's any better than RIAA?
Logged
qMopey
Level 6
*


View Profile WWW
« Reply #281 on: April 30, 2018, 01:25:47 PM »

Doesn't require constructors or destructors, or reference counting, or any other nonsense overkill bloated overly expressive features.
Logged
CodeLobe
Level 0
***


Decide to Act Sensibly


View Profile WWW
« Reply #282 on: April 30, 2018, 08:17:45 PM »

I've implemented something like that in a toy language before.  At work we have a C macro that does something like a "finally" block of try/catch/finally in some langs.  You'd put your free() call in the finally block.

The macro uses a nested function call (which is typically a NO-OP after the compiler gets done with it).  When the called function returns the macro's code is evaluated in the function before returning the results of the called function.  If it were me I'd have done it a bit differently, but it's not broke so...

Google's "Go" language also has deferred code:
https://blog.golang.org/defer-panic-and-recover
Quote
Code:
func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

Defer statements allow us to think about closing each file right after opening it,
guaranteeing that, regardless of the number of return statements in the function,
the files will be closed.

RAII (not RIAA as Crimsontide wrote -- unless they mean "Recording Industry Association of America®") is fine when your problemspace matches its idioms.  But when it doesn't match, then it sucks:  What if the program terminates outside of main() for some unknown reason?  Your code may be a library that doesn't have control of application life.  What if the file needs to be cleaned up while an exception has been thrown?  Can't really throw another exception.  Your program can't return two error codes at once to the shell either...  Basically, if you don't have to worry about anything going wrong, then RAII is great!  For non trivial "resources" RAII is often not your friend.

Really not a fault of RAII, it's the fault of how exception control flow is implemented by C++.

Note on Linux: malloc() (or new ...) never fails, so you typically don't have to worry about throwing out of memory exceptions.  The kernel will just start killing processes until you get the memory needed.  But what if that other process was the other end of an IPC pipe that your program must have to work?  So, anything can fail at anytime, esp. on embedded systems with limited resources to acquire or initialize.  That means the "R" of RAII can become de-initialized and may need to be re-initted any time, not just upon "aquisition" or when scopes are entered and exited.

Not trying to pick on C++ or RAII.  Just note that when everything starts looking like a nail, you're about to get screwed by a hammer.
Logged
Oats
Level 1
*


High starch content.


View Profile
« Reply #283 on: April 30, 2018, 08:30:07 PM »

I believe rust's memory management handles unexpected returns. I doesn't have exceptions but I believe some variations of it's panic! macro will peel the stack like an
exception would until reaching error handling code, but it still frees memory owned by the stack frames, identical to a way a finally block would.
Logged

eh
bexsella
Level 0
**



View Profile
« Reply #284 on: April 30, 2018, 09:25:40 PM »

I've implemented something like that in a toy language before.  At work we have a C macro that does something like a "finally" block of try/catch/finally in some langs.  You'd put your free() call in the finally block.

The macro uses a nested function call (which is typically a NO-OP after the compiler gets done with it).  When the called function returns the macro's code is evaluated in the function before returning the results of the called function.  If it were me I'd have done it a bit differently, but it's not broke so...

I imagine the background there's a stack of functions to be executed? My implementation at the moment requires an additional "return macro" which executes all functions on the stack in order of being added and returns the result requested. Functionally it's like this:

Code:
int main () {
  DEFER(say("Goodbye"));
  say("Hello");

  DEFER_RETURN(0);
}

With the expected output being:

Code:
Hello
Goodbye

Program return code 0.

It's ugly, and the code drives me up the wall.
Logged
Crimsontide
Level 5
*****


View Profile
« Reply #285 on: May 01, 2018, 05:31:18 AM »

Yeah sorry, meant RAII... tired when I wrote that.

"What if the program terminates outside of main() for some unknown reason?" - I can't imagine a scenario where other forms of flow control will work but RAII wouldn't.  Even in static initialization situations (ie. the only practical 'outside of main()' situation I can think of) RAII works fine.

"Your code may be a library that doesn't have control of application life." - RAII would still work...

"What if the file needs to be cleaned up while an exception has been thrown?" - That's exactly what RAII is designed to do, clean up, exceptions in particular.  The destructor of a File class would close the file handle.  If special clean-up code for a particular file was important, that's what try-catch blocks are for (when the default/RAII isn't sufficient).

"Can't really throw another exception." - Generally speaking you don't put code that can throw in a destructor.  But are we talking about exception handling vs error code/other stuff, or about RAII?  Because RAII works fine even if you choose to forgo exception handling.

"Basically, if you don't have to worry about anything going wrong, then RAII is great!" - RAII is great when things go wrong.  Interaction with exceptions is a HUGE part of what makes RAII so invaluable in C++.

I don't see how this:
Code:
void* mem = malloc(sz);
__run_on_scope_exit free(mem);

is preferable to this:
Code:
std::unique_ptr<int> mem(new int(5));

In the latter its more difficult to mess up...

I don't understand, while C++ has some big problems with it, I constantly see it being criticized for things it does well.
Logged
Daid
Level 3
***



View Profile
« Reply #286 on: May 01, 2018, 09:51:31 AM »

Doesn't require constructors or destructors, or reference counting, or any other nonsense overkill bloated overly expressive features.
std::unique_ptr does not use reference counting. The std::shared_ptr does.
Constructors are just a function call, and an empty inline one will be optimized away. Not sure why you would want uninitialized memory.
Destructors are just a function call as well, once again, the optimizer will make short work of it if it can be inlined.



Simply put, you are an old fart that hates every new way of doing things because you feel like you are no longer in control. While the actual thing is, you don't fully grasp it. And thus fall back to your old ways. Guess what, your old ways are slower to build anything, harder to maintain, more error prone.


Finally, in your example, you could use alloca. Only, you most likely want to keep the memory in some case, in which case you need to do extra stuff in your "on function exit" handler. Or, use std::unique_ptr, which does all the magic for you.
Logged

Software engineer by trade. Game development by hobby.
The Tribute Of Legends Devlog Co-op zelda.
EmptyEpsilon Free Co-op multiplayer spaceship simulator
qMopey
Level 6
*


View Profile WWW
« Reply #287 on: May 01, 2018, 10:17:43 AM »

They aren’t just function calls. For example, you cannot have a pointer to a constructor or destructor. The moment a constructor is built the user gets sucked into bunches of other C++ features that pop up due to warnings and compile errors. Often times just one constructor isn’t enough, and more must be made for different scenarios. Then the assignment operator will start to be needed. It’s a slippery slope.

Uninitialized memory is great. Without constructors VLAs are possible. Without constructors skipping over variable initialization (like with a goto) will not yield an error or warning.

I’m not old. I’m 26. I just have a different opinion than you, and can actually articulate my reasons for holding those opinions, rather than just claiming “more error prone, harder to maintain, slower to build” without any reasoning or data to back it up.
Logged
Crimsontide
Level 5
*****


View Profile
« Reply #288 on: May 01, 2018, 11:22:12 AM »

Simply put, you are an old fart that hates every new way of doing things because you feel like you are no longer in control. While the actual thing is, you don't fully grasp it. And thus fall back to your old ways.

Lets not insult each other.  We can disagree and do thing differently while still having civil discourse.
Logged
BorisTheBrave
Level 10
*****


View Profile WWW
« Reply #289 on: May 01, 2018, 01:21:49 PM »

There's a lot of dubious usages of RAII, just as with every C++ feature.

Good RAII is a wrapper around a pair of functions (like malloc/free, lock/unlock etc). It's simply a convenience over writing two lines of defer style statements. And it's zero overhead, works with exceptions, and generalizes better (unique_ptr has a lot of other use cases). What's not to like?

They aren’t just function calls. For example, you cannot have a pointer to a constructor or destructor. The moment a constructor is built the user gets sucked into bunches of other C++ features that pop up due to warnings and compile errors. Often times just one constructor isn’t enough, and more must be made for different scenarios. Then the assignment operator will start to be needed. It’s a slippery slope.
I really don't understand what you are talking about here. What's your point that constructors don't have normal pointers? Constructors get inlined just as much any normal function calls. Then, you seem to be saying that RAII is bad because it forces you to use other unspecified features, and they are bad for unspecified reasons.

Part of the beauty of C++ is that nearly all its features are low or zero overhead. You can take or leave what you like from it, and C++ is large enough that no one is going to what to take *all* of it. This even works on a library level. e.g. unique_ptr supports move semantics. But it also works just as well as a RAII guard, and you can ignore that aspect if you don't need it.
Logged
qMopey
Level 6
*


View Profile WWW
« Reply #290 on: May 01, 2018, 02:12:15 PM »

There's a lot of dubious usages of RAII, just as with every C++ feature.

Good RAII is a wrapper around a pair of functions (like malloc/free, lock/unlock etc). It's simply a convenience over writing two lines of defer style statements. And it's zero overhead, works with exceptions, and generalizes better (unique_ptr has a lot of other use cases). What's not to like?

What's not to like? In the case of a convenience wrapper like malloc/free, lock/unlock, there are still things to dislike. My issue is the convenience wrapper is an abstraction, which is almost always overlooked from an analytical perspective. Abstractions are always tied with an abstraction cost. Here the cost can be described like so:

  • Any code using the RAII wrapper is now dependent on the wrapper to compile. Either a header must be included, or some other way the code definition must be present.
  • Additional dependencies pose risks to code maturation. As code needs to change over time, code with any kind of dependencies becomes heavier to change. Even with a small RAII wrapper, the cost is real and still there. Sometimes the wrapper must be updated, or changed, or moved from one folder to another.d
  • Additional dependencies or abstractions make the code harder to port to other languages. If the algorithm proves useful perhaps it must be relocated to a new language, dragging along these wrappers with it. Often times the wrappers do not translate to other languages well, if at all, and new creative workarounds and engineering effort are needed to facilitate. Similarly, if the algorithms using these abstractions need to be expanded, or code ported from another language into C++, transforming them into the "wrapper friendly way" can be very cumbersome.
  • The abstraction of the wrapper is not ever going to be as ubiquitous as first-class citizens of the language specification. Since it's not officially supported out of the box by compilers, and instead, exists as a library/software extension, familiarity with the wrapper cannot be assumed. This means the code is less readable and more opaque when compared to basic malloc/free calls. This is not to say the algorithm is more or less elegant either way, or that the abstraction articulates an algorithm better or worse, but simply that the translation from C++ to assembly becomes opaque and hard to see through. Identifying exactly what the machine will be doing at any given moment with self-evident clarity will become a muddier task. If someone who happens to be unfamiliar with RAII, or with a specific wrapper, comes along to read code, it may not be immediately clear what is going on. It may take an extra 2-3 minutes to poke around and read the wrapper class source before understanding its use cases. I consider fragmenting user focus in this manner to be very costly.

These are just the points off the top of my head. There are certainly more points about abstraction cost and dependencies that we can talk about if we like.

They aren’t just function calls. For example, you cannot have a pointer to a constructor or destructor. The moment a constructor is built the user gets sucked into bunches of other C++ features that pop up due to warnings and compile errors. Often times just one constructor isn’t enough, and more must be made for different scenarios. Then the assignment operator will start to be needed. It’s a slippery slope.
I really don't understand what you are talking about here. What's your point that constructors don't have normal pointers? Constructors get inlined just as much any normal function calls. Then, you seem to be saying that RAII is bad because it forces you to use other unspecified features, and they are bad for unspecified reasons.

Part of the beauty of C++ is that nearly all its features are low or zero overhead. You can take or leave what you like from it, and C++ is large enough that no one is going to what to take *all* of it. This even works on a library level. e.g. unique_ptr supports move semantics. But it also works just as well as a RAII guard, and you can ignore that aspect if you don't need it.

I don't think I'm explaining myself here very well, and mostly just ranting.

The point I'm trying to make, is that constructors and destructors come along with a lot of linguistic baggage. They are not typical C functions, in any regard. I'm saying RAII isn't great, not because it forces the usage of unspecified features, but that it requires a software dependency (and an abstraction) and enforces very specific C++-style syntactic rules. Using constructors and destructors, using wrapper classes, using abstractions, these all have real costs associated with them. My conclusion is: I don't think RAII or similar mechanisms/wrappers have enough merit or value to justify these costs.

I don't see how this:
Code:
void* mem = malloc(sz);
__run_on_scope_exit free(mem);

is preferable to this:
Code:
std::unique_ptr<int> mem(new int(5));

In the latter its more difficult to mess up...

I don't understand, while C++ has some big problems with it, I constantly see it being criticized for things it does well.

std::unique_ptr requires many thousands of lines of code in header dependencies. These lines of code take time to compile. Compilation time also takes an extra hit due to template usage. Making use of unique_ptr is quite a C++ specific idea, so porting to/from other languages will be a little more difficult. For example, it's not C accessible. Maybe you specifically do not care about these costs, but some people do, and the costs are real and do exist.
« Last Edit: May 01, 2018, 02:20:34 PM by qMopey » Logged
Crimsontide
Level 5
*****


View Profile
« Reply #291 on: May 01, 2018, 04:25:31 PM »

std::unique_ptr requires many thousands of lines of code in header dependencies. These lines of code take time to compile. Compilation time also takes an extra hit due to template usage. Making use of unique_ptr is quite a C++ specific idea, so porting to/from other languages will be a little more difficult. For example, it's not C accessible. Maybe you specifically do not care about these costs, but some people do, and the costs are real and do exist.

Those 'costs' have nothing to do with RAII.  Lets not go off tangent.  You could easily make a standard library free version of unique_ptr if you didn't want the "many thousands of lines of code in header dependencies"; and if template's aren't your thing, again you could write a RIAA version without templates.

In fact, RAII would be faster compilation-wise, since only the header is required (a few dozen lines) with the implementation in another file, and you would be eliminating a large number of __run_on_scope_exit lines.  TBH I doubt the difference would even be measurable at the millisecond level, but if that 1/2 millisecond matters that much then RIAA would trump the workarounds.

I do agree that once you have a constructor/destructor, you pretty much have to have an assignment copy/move operator, and now you have the whole 'Rule of five' and all the issues that brings.  I can see that being a headache for situations where you don't want to create a full wrapper for just some simple exit code.  But for the bulk of resource management RAII is far superior in C++ compared to on_exit solutions.

In fact when I have to use other languages, its RAII and templates that I miss the most.  C# becomes a lisp-esque mess of embedded using's...

using(...) { using(...) {using(...) {using(...) {using(...) {using(...) {using(...) {using(...) {using(...) {}}}}}}}}} /// FCUK OFF!!! C#
Logged
qMopey
Level 6
*


View Profile WWW
« Reply #292 on: May 01, 2018, 04:36:11 PM »

To be clear, I'm not arguing that an ideal implementation of unique_ptr (one that does not currently exist) would be better than some weird __run_on_scope_exit thing in terms of compilation time. I'm just pointing out the current reality and the downsides therein.

That said, I agree with your last post Smiley
Logged
gimymblert
Level 10
*****


The archivest master, leader of all documents


View Profile
« Reply #293 on: May 02, 2018, 12:16:23 AM »

All bracket heavy language are too much lispesque already
I get it make it easier to define ending and starting of stuff for compiler, by removing ambiguity, but come on, I finnally understand I had to hide the bracket by putting them on the same line of the last command;}
Logged

CodeLobe
Level 0
***


Decide to Act Sensibly


View Profile WWW
« Reply #294 on: May 02, 2018, 08:17:55 AM »

I can't imagine a scenario where other forms of flow control will work but RAII wouldn't.

Well, then you should expand your horizons and start contributing to Linux's systems level code, or FLOSS drivers (we need the help).  One of thousands of examples: Interrupt handlers.  Basically contextless.  I HUP or SIGKILL you; Now, who you gonna call from a batch of code that MUST clean up some resources IMMEDIATELY?  From a .section that freely Executes at the request of the Operating System, beyond the control flow branching from main()?

My point is that RAII is fine when your execution flow is fairly simple.  Modern systems and complex programs have many paths of concurrent execution.  This has been prominent since at least the 70's... We solved that problem with C, and this is why it's still preferred among OS devs & system level programmers because no higher level programming concepts (not even ERLANG or GoLang), provide a complete encapsulation of the problemspace engulfing the sea of realities of modern program execution. 

Exceptions are just a way to encode another execution flow path, but it only allows for ONE path, and so does RAII, so it's incompatible with any modern operating environment because hardware interrupts exist.  At the systems level software must also contend with software interrupts, which are outside the scope of RAII and your exception stacks.  There is more to this world than the domed enclosure of C++; BE free()!  Explore!

Quote
"What if the file needs to be cleaned up while an exception has been thrown?" - That's exactly what RAII is designed to do, clean up, exceptions in particular.  The destructor of a File class would close the file handle.

"Can't really throw another exception." - Generally speaking you don't put code that can throw in a destructor.

Closing a file can throw an exception when file closing fails.  You really must report this failure to clean up or the system will fail on next reset.  However, your destructor was called due to an exception being thrown.  Game over.  Would you like to play again?

RAII, a strange game, the only winning move is not to play...
« Last Edit: May 02, 2018, 09:16:28 AM by CodeLobe » Logged
Daid
Level 3
***



View Profile
« Reply #295 on: May 02, 2018, 11:55:46 AM »

I'm pretty sure you can setup HUP/SIGKILL to only "message" you main context. After all, you don't do cleanup from an interrupt context, you could be half-way in a malloc for all you know. There is no safe way to interact with most of the state in your application from a signal. Which is why you generally only set a flag and then handle that flag from your main loop.
Generally, there are VERY strict rules of what you can do in an interrupt, or you run into all kinds of ugly race conditions. This is the case with or without RAII.

 Shocked you really think the way the linux kernel development is done is the best example for "this is the best use for game programming"?
Also, the linux kernel does manual reference counting everywhere. Most of the things done in the linux kernel is just manually doing what C++ can do for you without thinking about it, and without the ability to mess it up. Virtual functions, reference counting, exception handling. All done manual in the kernel, while C++ has facilities to handle this for you with less code = less things to go wrong.
There are good reasons the kernel is in C. ABI stability, compiler compatibility are just two of them. But legacy is a big reason.

RAII with reference counting makes your execution flow argument just plain... wrong. RAII allows very complex systems with multiple code paths. Just to name a random example, my textures and meshes in the thing I'm working on now, are std::shared_ptr, so as long as I still have 1 object that is using the mesh or texture, it lives in memory. As soon as the last object stops using it, it gets deleted and reloaded on the next time an object wants it (thanks to the weak pointer)



Closing a file can throw an exception when file closing fails.  You really must report this failure to clean up or the system will fail on next reset.  However, your destructor was called due to an exception being thrown.  Game over.  Would you like to play again?
The exception from a close is to indicate that the data was not properly/fully written. So yes, there is an issue there. Still, you can handle that exception in the destructor fine, you just cannot raise it higher, which makes sense, as your object controls that resource. If the destructor does not have the knowledge to handle this destruction, you more likely have an OOP design issue.
If, for example, you now have a messed up save-file. Then you haven't followed safe file writing procedures. Directly overwriting a file is asking for problems if you want super high reliability (write new file, rename after writing&syncing). Else, if you can take the risk, this exception on close is no real concern to you anyhow. As it generally means the disk you are trying to use to store data can no longer be written to.
Logged

Software engineer by trade. Game development by hobby.
The Tribute Of Legends Devlog Co-op zelda.
EmptyEpsilon Free Co-op multiplayer spaceship simulator
Crimsontide
Level 5
*****


View Profile
« Reply #296 on: May 02, 2018, 11:59:57 AM »

I think you are confusing exceptions and RAII.

While I wouldn't use RAII in an interrupt (AFAIK the last interrupt I coded was in assembly), RAII is just a function call (at worst, often just code due to inlining).  So if you can't use function calls, then of course you can't use RAII, but I don't think anyone would argue to avoid using functions simply because deep in some OS handler and/or embedded interrupt you can't use call...

beyond the control flow branching from main()
Again this whole 'flow control beyond main()' thing.  It doesn't matter if the code is executed within main or not.  It can be in a service handler, in a library, in a shared lib, whatever, if you can 'call/ret' then you can RAII.

There's nothing magical going on here.  The scope ends, the RAII destructors get called in reverse order, then the function returns.  As long as you have a stack, you're good to go; and if you don't have a working stack, then you better be using assembly because otherwise things are going to get messy.

Quote
Exceptions are just a way to encode another execution flow path, but it only allows for ONE path, and so does RAII,
RAII is not exceptions.  While exception handling does branch code to a completely different code path, RAII doesn't necessarily, and usually shouldn't.

Quote
so it's incompatible with any modern operating environment because hardware interrupts exist
Which can happily accommodate RAII.  And even if you don't want to use RAII in an interrupt handler, that doesn't mean you shouldn't use it the other 99.999999999999999999999999999999999999999999999999999999999% of the time.

Quote
At the systems level software must also contend with software interrupts, which are outside the scope of RAII and your exception stacks.
/sigh.. ok... repeat after me.  RAII IS NOT EXCEPTIONS.

Quote
There is more to this world than the domed enclosure of C++; BE free()!  Explore!
By all means, but at least if you're going to criticize a feature at least understand it.

Quote
Closing a file can throw an exception when file closing fails.  You really must report this failure to clean up or the system will fail on next reset.  However, your destructor was called due to an exception being thrown.  Game over.  Would you like to play again?

RAII, a strange game, the only winning move is not to play...
You... you're trolling me right?  You're not serious?
Logged
CodeLobe
Level 0
***


Decide to Act Sensibly


View Profile WWW
« Reply #297 on: May 05, 2018, 02:24:38 AM »

Your condescending tone is unwarranted.

I think you are confusing exceptions and RAII.

No, I'm pointing out the problemspace where exceptions (unwinding the call stack) and RAII meet.

Quote
While I wouldn't use RAII in an interrupt (AFAIK the last interrupt I coded was in assembly),

Signal handlers are documented as "software interrupts" or "OS interrupts" since they are implemented similarly (an address is registered as a place to call into when a given event occurs).  You can write these as well as hardware interrupt tables in C, (or C++ if you like).
 
Quote
RAII is just a function call (at worst, often just code due to inlining).  So if you can't use function calls, then of course you can't use RAII, but I don't think anyone would argue to avoid using functions simply because deep in some OS handler and/or embedded interrupt you can't use call...

RAII is not: "just a function call".  RAII is pairing initialization of a resource with acquiring (often allocating) the resource, AND utilizing automatically generated code which calls destructors prior to leaving a function as your resource releasing method.    RAII dictates a lifecycle of "resources" which is tied to program execution flow, and is usually not easily compatible with heavy resources, like sockets or files or memory-mapped IPC, etc.  Works fine for trivial RAM-only resources, but RAII is not a panacea for all your resource management ills. That the gist I'm getting at.   

Quote
beyond the control flow branching from main()
Again this whole 'flow control beyond main()' thing.  It doesn't matter if the code is executed within main or not.  It can be in a service handler, in a library, in a shared lib, whatever, if you can 'call/ret' then you can RAII.

If you're using RAII it means your clean up code happens prior to a function returning.  This conflicts with the potential of calling cleanup code from a different context such as in a signal handler or other interrupt (such as the floating point exception handler).  My point is that this is one instance where RAII will not work.  If you delete some RAM or .close() a file, etc. due to SIG_HUP or SIG_TERM, etc, and then you used RAII elsewhere to delete or .close() or otherwise release the same resources, then you've got a problem.  That problem is called: Manually managing resource life cycle, and you'll be writing code that obviates the need for RAII to solve that issue.

Quote
There's nothing magical going on here.  The scope ends, the RAII destructors get called in reverse order,
That's the issue.  Computers do more than one thread of execution and even a single core processor runs on an OS which can preempt execution to call hardware / software interrupts, notify of system shutdown, query for execution status, notify of IPC, mouse/keyboard event pumps, etc.

Quote
Quote
Exceptions are just a way to encode another execution flow path, but it only allows for ONE path, and so does RAII,
RAII is not exceptions.  While exception handling does branch code to a completely different code path, RAII doesn't necessarily, and usually shouldn't.
If you use RAII with C++ you're probably going to have to contend with the fact that an exception can be the cause of executing RAII clean up code.  This isn't hard to understand.  RAII generated cleanup code can be called due to an exception being thrown.  I never said RAII is an exception.  This is a misunderstanding on your part.  I'm explicitly talking about RAII clean up code being called in the context of an exception unwinding the stack.

Quote
Quote
Closing a file can throw an exception when file closing fails.  You really must report this failure to clean up or the system will fail on next reset.  However, your destructor was called due to an exception being thrown.  Game over.  Would you like to play again?

RAII, a strange game, the only winning move is not to play...
You... you're trolling me right?  You're not serious?

Let's do a quick check:
http://www.cplusplus.com/reference/fstream/ofstream/close/

Quote
Exception safety
Basic guarantee: if an exception is thrown, the stream is in a valid state.
Any exception thrown by an internal operation is caught by the function and rethrown after closing the file.
It throws an exception of member type failure if the function fails (setting the failbit state flag) and member exceptions was set to throw for that state.

You're now aware that closing a C++ fstream can throw an exception.  You previously stated you'd put the file closing stuff in the destructor.  If an exception is thrown and this unwinds your stack, calling fstream's destructor thanks to RAII, then your stated solution is at odds with not only "don't put code in destructor that can throw" but also with the rule "you can't really throw another exception while an exception is unwinding the stack".  Let's say you put a try/catch in the destructor of a wrapper class to close the fstream.  Now you can't report the failure to close the file.  Point being: the RAII clean up code is throwing an exception.  If not using RAII you could wrap the fstream.close() in a try/catch and report the failure to close even when your try/catch .close() is called due to an exception being thrown.

I'm done with this discussion.  Sorry for blaspheming your god, but my criticism was valid.
Logged
Daid
Level 3
***



View Profile
« Reply #298 on: May 05, 2018, 05:02:29 AM »

Quote
Your condescending tone is unwarranted.
Actually. It is. You pretty much asked for it. As you show "ivory tower" behavior. You claim something is bad, without compromise. List a few examples, and then disregard a whole industry accepted method of doing things because of those few examples.

People went as far as telling you there is some reality in those examples, but claim those examples are uncommon problems.

To get out the car analogy, you say a Smart is a bad vehicle, because it cannot drive in rough terrain. While other people say, well, yes, there is rough terrain, but not between me and the supermarket.


You fail to show any empathy to understand the other side of looking at it. And keep repeating the same examples to force your view on people. So yes. You earn the condescending tone. You worked hard for it.
Logged

Software engineer by trade. Game development by hobby.
The Tribute Of Legends Devlog Co-op zelda.
EmptyEpsilon Free Co-op multiplayer spaceship simulator
Crimsontide
Level 5
*****


View Profile
« Reply #299 on: May 05, 2018, 07:55:37 AM »

Your condescending tone is unwarranted.
I was exasperated/flabbergasted, not trying to be condescending but tone is hard to convey over the interwebs, so I apologize if it came across as condescending.

Quote
No, I'm pointing out the problemspace where exceptions (unwinding the call stack) and RAII meet.
That only exists if you choose it to.  You can disable exceptions at the compiler level.  Stating that RAII is broken because exceptions can double throw is not a valid argument.  Not only are they separate features, but its also near impossible to accidentally throw from a destructor.

Quote
Signal handlers are documented as "software interrupts" or "OS interrupts" since they are implemented similarly (an address is registered as a place to call into when a given event occurs).  You can write these as well as hardware interrupt tables in C, (or C++ if you like).
Similar, but not the same.  Signal handlers can handle RAII without any problems (and exceptions with a little care).

Quote
RAII is not: "just a function call".  RAII is pairing initialization of a resource with acquiring (often allocating) the resource, AND utilizing automatically generated code which calls destructors prior to leaving a function as your resource releasing method.
Exactly... a function call.  From a theoretical perspective, sure it means a lot more, but on the practical end its just that, a simple function call (often inlined).  A destructor call doesn't magically jump to other threads/contexts, it executes in the context of the function that the object was created in.

Quote
RAII dictates a lifecycle of "resources" which is tied to program execution flow, and is usually not easily compatible with heavy resources, like sockets or files or memory-mapped IPC, etc.  Works fine for trivial RAM-only resources, but RAII is not a panacea for all your resource management ills. That the gist I'm getting at.
It works anywhere a _execute_on_scope_exit(...) type macro/function/intrinsic would.

Quote
If you're using RAII it means your clean up code happens prior to a function returning.  This conflicts with the potential of calling cleanup code from a different context such as in a signal handler or other interrupt (such as the floating point exception handler).  My point is that this is one instance where RAII will not work.  If you delete some RAM or .close() a file, etc. due to SIG_HUP or SIG_TERM, etc, and then you used RAII elsewhere to delete or .close() or otherwise release the same resources, then you've got a problem.  That problem is called: Manually managing resource life cycle, and you'll be writing code that obviates the need for RAII to solve that issue.
See, you're jumping all around conflating issues from multiple sources.

Ok lets go with this file class example.  Its not uncommon to have a Close() function which closes an opened file.  This would be an optional function.  If you destroy an opened file object, the destructor would close the file, no exceptions thrown, no way to indicate an error occurred if it occurred.  Close() on the other hand throws an exception if an error occurs, otherwise it closes successfully.  Either way there's no risk of double destruction, resource leakage, or double exception throws.

Why does this work?  Because its pretty trivial for the destructor to track the state of the object and react accordingly.  In fact that's kinda the point of a destructor.

Now if you want to allocate resources in one thread/context and clean them up in another, just new/delete (or use a shared_ptr).
 I'm not saying that naive RAII handles every resource clean-up situation ever, but it does handle the VAST MAJORITY.

Quote
That's the issue.  Computers do more than one thread of execution and even a single core processor runs on an OS which can preempt execution to call hardware / software interrupts, notify of system shutdown, query for execution status, notify of IPC, mouse/keyboard event pumps, etc.
None of which affects RAII.  If a thread is pre-empted to run an interrupt, irregardless of whether it was in a destructor or not, it still has to resume execution as if it hadn't been pre-empted.  If it didn't, even without RAII, you'd have programs breaking all over the place.  And if you're working with a shared resource, such that a pre-emption could cause corruption or unforeseen state change, then you better protect it with a mutex (or similar).  But again, it has nothing to do with RAII.

Quote
If you use RAII with C++ you're probably going to have to contend with the fact that an exception can be the cause of executing RAII clean up code.  This isn't hard to understand.  RAII generated cleanup code can be called due to an exception being thrown.  I never said RAII is an exception.  This is a misunderstanding on your part.  I'm explicitly talking about RAII clean up code being called in the context of an exception unwinding the stack.
You keep bringing up problems with exceptions, and conflating them with problems with RAII.  You can double-throw even without RAII.  In fact its actually near impossible to accidentally double throw from a destructor.

https://akrzemi1.wordpress.com/2011/09/21/destructors-that-throw/

There's this weird paranoia surrounding exceptions for some reason.  Repeatedly I read weak arguments against exceptions followed by the usual 'bang your dead' or 'game over' or something to that effect.  Its not a big deal.  If you're in a situation where your exception handling code failed (either due to a file error, lack of memory, or a device/driver broke) there's little you can do apart from std::terminate() anyways (and don't start with the 'but what about hardware interrupts'... no one's suggesting using exceptions in hardware interrupts).
 
Quote
It does exactly what one would expect...  I don't understand the issue.  If proper closing of the file matters (ie. you are writing to it not just reading, or its not a temp file, or is necessary in some manner) and you can/want to recover if it fails, you call close().  If you don't care, you don't.  Either way, the destructor doesn't need to throw, and can clean up if necessary.

Quote
You're now aware that closing a C++ fstream can throw an exception.
Calling close() can, not destructing it.

Quote
You previously stated you'd put the file closing stuff in the destructor.  If an exception is thrown and this unwinds your stack, calling fstream's destructor thanks to RAII, then your stated solution is at odds with not only "don't put code in destructor that can throw" but also with the rule "you can't really throw another exception while an exception is unwinding the stack".  Let's say you put a try/catch in the destructor of a wrapper class to close the fstream.  Now you can't report the failure to close the file.  Point being: the RAII clean up code is throwing an exception.  If not using RAII you could wrap the fstream.close() in a try/catch and report the failure to close even when your try/catch .close() is called due to an exception being thrown.

If you're going to talk in hypotheticals, then stick to that.  If you want specifics about a particular implementation, then stick to that; but you can't switch back and forth at a whim in an attempt to point out 'contradictions'.  My 'put file close code in the destructor' response was a simple/short response to a hypothetical scenario, it wasn't meant as an in-depth response on the intricacies and design decisions of ofstream.

I've already pointed out the obvious solution above, and that is exactly that ofstream does.  There's no contradiction.  IMO its logical, safe, and easier to use than other solutions.

Quote
I'm done with this discussion.  Sorry for blaspheming your god, but my criticism was valid.

All I got was:

- You don't think exceptions should be used in hardware interrupts. Which I agree with, but I have no idea what it has to do with RAII in the context of a game development forum.

- You seem to imply that destructors can cause context/thread switching (somehow via software interrupts/signal handlers), but don't seem to want to state it explicitly.  You keep bringing up this idea that handlers/interrupts or other types of ambiguously defined things can cause problems with RAII, but haven't stated how or why.  I'm still unsure whether you think that signal/interrupts handler code should not have RAII, or that an interrupt/pre-emption from a signal/interrupts handler in a destructor call is the issue.  This branched off into the 'what if I want to destroy resources in the context of a handler/interrupt' but again, did you mean allocate in one context, destroy in another?  Or... I don't know really.  These are such esoteric situations that you'll need to be much more precise.

- You don't like exceptions.

Was there something I missed?
Logged
Pages: 1 ... 13 14 [15] 16
Print
Jump to:  

Theme orange-lt created by panic