Quick question: C++ compare to null?

Page 2 - Seeking answers? Join the AnandTech community: where nearly half-a-million members share solutions and discuss the latest tech.

degibson

Golden Member
Mar 21, 2008
1,389
0
0
Originally posted by: Templeton
Originally posted by: degibson

Sure. C++ has auto_ptr for instance: pointers for the pointer-challenged programmer.
http://www.cplusplus.com/reference/std/memory/auto_ptr/

It is near impossible to write exception safe code in c++ without RAII, smart pointers of some type are required to keep from leaking in the face of exceptions without resorting to try/catch at every level. C++0x/tr1 includes support for additional smart pointers based largely on those found in boost.

The 'exception' language abstraction is sadly bolted on to C++.

I generally see C++ as a syntactic wrapper around C, in turn a syntactic wrapper around assembly. As such, I generally use C-style failure and recovery techniques (i.e. check everything, catch all signals, etc.) I realize that is not exactly the 'aim' of an OO language, but does have the virtue of being very close to reality. In particular, exceptions over-abstract failures. The notion of 'exceptions' make a lot more sense in a managed language (e.g. java) where there is a 'runtime' to throw exceptions in the first place, rather than (just) hardware that spits out virtual memory translation failures.
 

Venix

Golden Member
Aug 22, 2002
1,084
3
81
Originally posted by: degibson
Originally posted by: Templeton
Originally posted by: degibson

Sure. C++ has auto_ptr for instance: pointers for the pointer-challenged programmer.
http://www.cplusplus.com/reference/std/memory/auto_ptr/

It is near impossible to write exception safe code in c++ without RAII, smart pointers of some type are required to keep from leaking in the face of exceptions without resorting to try/catch at every level. C++0x/tr1 includes support for additional smart pointers based largely on those found in boost.

The 'exception' language abstraction is sadly bolted on to C++.

I generally see C++ as a syntactic wrapper around C, in turn a syntactic wrapper around assembly. As such, I generally use C-style failure and recovery techniques (i.e. check everything, catch all signals, etc.) I realize that is not exactly the 'aim' of an OO language, but does have the virtue of being very close to reality. In particular, exceptions over-abstract failures. The notion of 'exceptions' make a lot more sense in a managed language (e.g. java) where there is a 'runtime' to throw exceptions in the first place, rather than (just) hardware that spits out virtual memory translation failures.

How is the C++ runtime throwing std::exception any different from Java's runtime throwing Exception? Hardware exceptions (access violations, divide by 0, priviliged instructions) are not handled by the standard C++ exception mechanism, so I'm not sure what you mean by the virtual memory comment. C++ exceptions are thrown by operator new and the standard library; i.e. the C++ runtime.

The only major difference between C++'s and Java's exception mechanisms (besides forced checked exceptions) is that C++ allows sensible rollback and cleanup with RAII/ScopeGuard, whereas Java developers are stuck using the horrid try/finally garbage for cleanup and try/catch/rethrow for rollback.
 

Markbnj

Elite Member <br>Moderator Emeritus
Moderator
Sep 16, 2005
15,682
14
81
www.markbetz.net
Originally posted by: degibson
if(!next) is very C-esque. An explicit NULL comparison is better, even more so with NULL on the left.

A colleague taught me to do this years ago, and I still do (place the constant or literal on the left of a comparison to catch accidental assignment), but I've never worked with anyone else who does this.
 

Markbnj

Elite Member <br>Moderator Emeritus
Moderator
Sep 16, 2005
15,682
14
81
www.markbetz.net
Originally posted by: Venix
The only major difference between C++'s and Java's exception mechanisms (besides forced checked exceptions) is that C++ allows sensible rollback and cleanup with RAII/ScopeGuard, whereas Java developers are stuck using the horrid try/finally garbage for cleanup and try/catch/rethrow for rollback.

I might argue this point on the general principle that explicit behavior is preferable to side effects. A finally block makes it very clear what has to happen before the block is exited. In any event it's apples and oranges isn't it? We wouldn't need finally in C# if we had C++-style destruction semantics, but I'll take finally in return for the benefits of a managed environment.

But I do agree that checked exceptions in Java suck.

 

Venix

Golden Member
Aug 22, 2002
1,084
3
81
Originally posted by: Markbnj
Originally posted by: Venix
The only major difference between C++'s and Java's exception mechanisms (besides forced checked exceptions) is that C++ allows sensible rollback and cleanup with RAII/ScopeGuard, whereas Java developers are stuck using the horrid try/finally garbage for cleanup and try/catch/rethrow for rollback.

I might argue this point on the general principle that explicit behavior is preferable to side effects. A finally block makes it very clear what has to happen before the block is exited. In any event it's apples and oranges isn't it? We wouldn't need finally in C# if we had C++-style destruction semantics, but I'll take finally in return for the benefits of a managed environment.

But I do agree that checked exceptions in Java suck.

Deterministic destruction and a managed environment aren't mutually exclusive. C++/CLI provides a decent implementation of deterministic destruction for .NET through stack-like objects and a delete operator. It's implemented using IDisposable (like C#'s 'using' keyword), but unlike 'using' it also works for member variables so you can easily propagate the "IDisposable-ness" of a member up to its parent class.

There's also no real reason that a managed environment has to use horrible try/catch/rethrow for rollback when better alternatives (ScopeGuard, ScopeExit) have existed for years. ScopeGuard is implementable in .NET with IDisposable and lambdas or anonymous delegates, but as far as I know it's not possible in Java.
 

degibson

Golden Member
Mar 21, 2008
1,389
0
0
Originally posted by: Venix
How is the C++ runtime throwing std::exception any different from Java's runtime throwing Exception?

Java can implement exceptions any way the underlying JVM wants, so long as the semantics of the try/catch block are maintained. One good way is to treat try/catch as no-ops, then look for them explicitly lower in the call stack when/if something goes wrong (e.g. an exception is thrown). C++ doesn't have that flexibility -- the only way to return to the try (in general) is to setjmp()/longjmp() (ignoring side effects, of course), which gives try/catch a pretty big runtime overhead in the common error-free case.

Hardware exceptions (access violations, divide by 0, priviliged instructions) are not handled by the standard C++ exception mechanism, so I'm not sure what you mean by the virtual memory comment.

Exactly! I was pointing out that, in unmanaged languages, the programmer must avoid or otherwise handle problems that aren't abstracted by 'exceptions'. That is, there is little to be gained by adding exceptions to vanilla C++, as they cannot be used to handle all failures, especially considering how much they complicate the memory model. My virtual memory comment was an example of a true hardware exception from which the C++ runtime does not recover. One cannot 'catch' SIGSEGV. (One can recover from such a problem, if you want to install a segfault handler... it is therefore possible to make SIGSEGV catchable, but only with a setjmp())

C++ exceptions are thrown by operator new and the standard library; i.e. the C++ runtime.
Agreed. They're just objects. The throw keyword just wraps new and longjmp(). Its akin to a goto, or a fast POSIX signal. But as I said above, they're inferior to exceptions in higher languages, because one cannot 'catch' all error conditions.

The only major difference between C++'s and Java's exception mechanisms (besides forced checked exceptions) is that C++ allows sensible rollback and cleanup with RAII/ScopeGuard, whereas Java developers are stuck using the horrid try/finally garbage for cleanup and try/catch/rethrow for rollback.

I would say that RAII/ScopeGuard add those features to disciplined C++, when/if they are used. They are not inherent features of C++ (sensible rollback? Bah!). If you're going to use disciplined C++, that opens the door to other ways of reporting/handling errors (e.g., like they do in C).

Incidentally, I certainly am not a fan of Java's try/catch/finally, either, but they make sense in a language that isn't intended to run native most of the time.

EDIT: Grammar
 

Markbnj

Elite Member <br>Moderator Emeritus
Moderator
Sep 16, 2005
15,682
14
81
www.markbetz.net
Originally posted by: Venix
Originally posted by: Markbnj
Originally posted by: Venix
The only major difference between C++'s and Java's exception mechanisms (besides forced checked exceptions) is that C++ allows sensible rollback and cleanup with RAII/ScopeGuard, whereas Java developers are stuck using the horrid try/finally garbage for cleanup and try/catch/rethrow for rollback.

I might argue this point on the general principle that explicit behavior is preferable to side effects. A finally block makes it very clear what has to happen before the block is exited. In any event it's apples and oranges isn't it? We wouldn't need finally in C# if we had C++-style destruction semantics, but I'll take finally in return for the benefits of a managed environment.

But I do agree that checked exceptions in Java suck.

Deterministic destruction and a managed environment aren't mutually exclusive. C++/CLI provides a decent implementation of deterministic destruction for .NET through stack-like objects and a delete operator. It's implemented using IDisposable (like C#'s 'using' keyword), but unlike 'using' it also works for member variables so you can easily propagate the "IDisposable-ness" of a member up to its parent class.

There's also no real reason that a managed environment has to use horrible try/catch/rethrow for rollback when better alternatives (ScopeGuard, ScopeExit) have existed for years. ScopeGuard is implementable in .NET with IDisposable and lambdas or anonymous delegates, but as far as I know it's not possible in Java.

I find this discussion very interesting. Fundamentally I disagree that the "try/catch/rethrow" paradigm is horrible. People have been arguing about where to catch exceptions and what to do with them since the concept was first introduced. I've yet to hear anyone propose the "right way" to handle errors. It doesn't require much imagination to come up with situations where catching and rethrowing exceptions is a useful paradigm. In a current project we have a number of distributed components, and a logging service they all write to. At almost every level of the code we're catching exceptions, logging what we were doing and what the exception contains, and then passing them on up the stack.

The other part of your argument just seems to me to impose the constraints of unmanaged C++ on managed environments. We can talk about different kinds of resources, but really most of the motivation for RAII and hacks like ScopeGuard stems from the requirement in C++ to allocate and free memory. In managed languages we don't care about memory, which means we don't care about 90+% of the objects in the graph. The GC handles them just fine. But it can't make decisions about streams, database connections, and other external resources. In those cases you want to tie the lifetime of the resource to a scope, and IDisposable provides the mechanism for doing that. From my perspective it doesn't make sense to alter the way the language treats all objects in order to call Dispose automatically on the few objects that require it. Nor does it make sense to jump through syntactic hoops to try and turn it into a side-effect. It's a little kludgy, sure, but it was a trade-off and I think the designers made the right choice.
 

Venix

Golden Member
Aug 22, 2002
1,084
3
81
Originally posted by: degibson
Java can implement exceptions any way the underlying JVM wants, so long as the semantics of the try/catch block are maintained. One good way is to treat try/catch as no-ops, then look for them explicitly lower in the call stack when/if something goes wrong (e.g. an exception is thrown). C++ doesn't have that flexibility -- the only way to return to the try (in general) is to setjmp()/longjmp() (ignoring side effects, of course), which gives try/catch a pretty big runtime overhead in the common error-free case.

You could use setjmp/longjmp to implement exceptions, but modern compilers use more optimal techniques. For example, MSVC uses the existing Windows Structured Exception Handling mechanism along with a state variable to determine which destructors to call when the stack is unwound. The overhead is minimal, and even less significant when you consider that you no longer have a ton of branches from checking error codes. You can even implement exceptions with no runtime cost in the common case--appropriately called "zero cost" exceptions--but the cost of throwing and the additional size of the executable is often prohibitive.

The compilers for managed languages can certainly make more aggressive optimizations than C++ compilers in many cases, but that's pretty much just the general case for JIT compiling.

Originally posted by: degibson
Exactly! I was pointing out that, in unmanaged languages, the programmer must avoid or otherwise handle problems that aren't abstracted by 'exceptions'. That is, there is little to be gained by adding exceptions to vanilla C++, as they cannot be used to handle all failures, especially considering how much they complicate the memory model. My virtual memory comment was an example of a true hardware exception from which the C++ runtime does not recover. One cannot 'catch' SIGSEGV. (One can recover from such a problem, if you want to install a segfault handler... it is therefore possible to make SIGSEGV catchable, but only with a setjmp())\

You could translate SIGSEGV into an exception like Java does or use _set_se_translator on Windows. Or let the program crash, because gracefully unwinding the stack when you segfault probably isn't recommended.

I'm not really seeing how this is an indictment of C++'s exception mechanism. Exceptions are intended to handle runtime error conditions, not coding mistakes like those that cause segfaults. Separate handling of runtime errors and programmer/system screw-ups seems reasonable, sort of like Java's separate 'Error' class hierarchy for "your program is completely screwed" errors.
 

Venix

Golden Member
Aug 22, 2002
1,084
3
81
Originally posted by: Markbnj
I find this discussion very interesting. Fundamentally I disagree that the "try/catch/rethrow" paradigm is horrible. People have been arguing about where to catch exceptions and what to do with them since the concept was first introduced. I've yet to hear anyone propose the "right way" to handle errors. It doesn't require much imagination to come up with situations where catching and rethrowing exceptions is a useful paradigm. In a current project we have a number of distributed components, and a logging service they all write to. At almost every level of the code we're catching exceptions, logging what we were doing and what the exception contains, and then passing them on up the stack.

Certainly there are situations where catching and rethrowing an exception is useful. What isn't useful is being forced to catch and rethrow just to perform rollback. Consider this scenario: you have to perform three actions, and if any of them throws an exception the completed actions must be undone. This is trivial in C++ but pretty annoying in Java.

Originally posted by: Markbnj
The other part of your argument just seems to me to impose the constraints of unmanaged C++ on managed environments. We can talk about different kinds of resources, but really most of the motivation for RAII and hacks like ScopeGuard stems from the requirement in C++ to allocate and free memory. In managed languages we don't care about memory, which means we don't care about 90+% of the objects in the graph. The GC handles them just fine. But it can't make decisions about streams, database connections, and other external resources. In those cases you want to tie the lifetime of the resource to a scope, and IDisposable provides the mechanism for doing that. From my perspective it doesn't make sense to alter the way the language treats all objects in order to call Dispose automatically on the few objects that require it. Nor does it make sense to jump through syntactic hoops to try and turn it into a side-effect. It's a little kludgy, sure, but it was a trade-off and I think the designers made the right choice.

IDisposable and C#'s 'using' is a decent solution, although I still find C++/CLI's solution to be clearer and more useful. Java is unfortunately stuck with try/finally.

It's also not just about resources. There are times where you have some kind of End* function that must be called after Begin*, and there is the rollback scenario I mentioned above. RAII or some kind of built-in scope exit feature would be a simple and natural solution. I simply see no point in the language forcing you to write six additional lines of code and to introduce a new scope just to say "do this on scope exit".

Granted, my complaints are mainly about syntax rather than actual functionality, but I find that bloated, crappy syntax really hurts a language's usability. (Yes, that is funny coming from someone who is defending C++)
 

degibson

Golden Member
Mar 21, 2008
1,389
0
0
Originally posted by: Venix
You could use setjmp/longjmp to implement exceptions, but modern compilers use more optimal techniques. ...
I should have been clearer about this. setjmp()/longjmp() is the only mechanism I know of to handle the general case of exceptions -- i.e. exceptions that can occur in code not under compilation right now. If the compiler has access to all source, and does cross-file optimization, then its possible to use way lighter mechanisms (aka simple pc-relative branches, variables on the stack, etc.).

You could translate SIGSEGV into an exception like Java does or use _set_se_translator on Windows.
Yes, I suggested this was possible and plausible in C++. However, Java doesn't need to take the SIGSEGV in the first place, as it has no notion of a pointer. Only Java-translated-to-native has the problem. Moreover, doing this manually is a real PITA (I know from experience -- from signal handler to 'catch' clause... hard, ugly, and not as useful as you might think, because of side effects from the bad memory access in the first place.)

I'm not really seeing how this is an indictment of C++'s exception mechanism. Exceptions are intended to handle runtime error conditions, not coding mistakes like those that cause segfaults. Separate handling of runtime errors and programmer/system screw-ups seems reasonable, sort of like Java's separate 'Error' class hierarchy for "your program is completely screwed" errors.
Different strokes, I suppose. I look at almost all errors (e.g. segfault, broken pipe, file not found) as equal:
1) Avoid if possible
2) Handle when necessary
3) Do not ignore

Realistically, in a big project, there will be bugs (even segfaults) that cannot be avoided -- either because they cannot be debugged (e.g. heisenbug), are entirely unknown, or are prohibitively expensive to remove. They can only be handled. This is the vision of the 'exception'. But, since its not entirely general in C++, I find them to be lacking and therefore I resort to more general techniques.

 

degibson

Golden Member
Mar 21, 2008
1,389
0
0
Originally posted by: Markbnj
I've yet to hear anyone propose the "right way" to handle errors.

I appreciate the database approach to error handling. Make an invariant that is provably correct (e.g. logs as in Paxos) and obstruction-free, even in the presence of crashes and failures. Then, apply all the bug-avoidance strategies you want on top of that invariant to get performance. While a heavyweight commit log isn't necessarily the right approach for all applications, the coding discipline that makes it possible in the first place is: fault-tolerant code needs to be written with fault tolerance in mind from the ground up (I'm speaking of native code here... managed is another ball game altogether of course).