Tell me about C++ features and quirks that I didn't know

Started by
107 comments, last by szecs 14 years, 5 months ago
Quote:Original post by mazelle
What do you mean undefined? It prints
0
2


int a=0;std::cout << ++a + ++a << std::endl;


Try that in gcc, and then try it in visual C++. You'll get two different answers. VC++ performs the pre increments first, giving 2+2. gcc increments 1 arg of the binary add, and then the other giving: 1 + 2. The behaviour is undefined as per the C++ spec, which is why compilers treat it differently.
Advertisement
Quote:Original post by Washu
Quote:Original post by Kwizatz
Quote:Original post by Washu
Uh, what on earth are you going on about? The the valid range of pointers within p is [p, p+10]. Note the brackets are "INCLUSIVE" not exclusive. Exclusive would use ().


Fair enough, so do you consider k to be in range? does k point to a valid address?

*** Source Snippet Removed ***


Provided k is within the range of valid values for a pointer derived from p, that is [p, p+10], k is valid. You do not have to be able to DEREFERENCE a pointer for it to be valid. The pointer ONE PAST THE END is valid. Just not dereferenceable. The problem is that you've made the assumption that for a pointer to be valid it must also be dereferenceable, which it does not.

I am enlightened. Thank you.
Quote:
Yeah, the C++ idiom of iterators is based on having a begin and an end marker. For the iterator type for an array, the "end iterator" is the "one past the end of the array pointer".

Noted.

C++ is hard.
BTW, I just had my proficiency this afternoon. They just let me code a simple game with OOP concepts and such. I think I did very well, though. I hope they call me for the tech interview.

This thread has shed light to me to the inner workings of C++. I never cared before. I just use what works.

This seems to be a thread ender post already, but I don't want this thread to end. I hope you could still add some mind blowing stuff that most programmers don't know.

I wanted to ask this:
void doSomething( SomeClass& instance ){  ...}SomeClass returnsByValue(){...}doSomething( returnsByValue() );


This should be illegal based from C++ Primer because the parameter in doSomething() can't hold unnamed variables. But it works fine in VC9.

Is this one of those undefined stuff?
On the topic of iterators, a while back Alexandrescu gave a really interesting talk about them, and their alternatives. Required viewing for anyone who's interested in C++'s potential for high-level programming. Check it out here. Slides available here.
Quote:Original post by mazelle
But it works fine in VC9.

Is this one of those undefined stuff?

No, it's a horrible extension of the Microsoft Compiler that violates the standard.
Quote:Original post by Zipster
It would be difficult to use this very common loop idiom if it wasn't a valid pointer:

for(int* k = p; k < (p + 10); ++k) { ... }

You could also use a "!=" check. The idea is that (p + 10) acts as a sentinel to indicate the end of iteration. At least that's my best guess.


Gotchya! I understand the sentinel concept and this fits in with that. It makes more sense now.

Thanks!
More features/quirks:

-function references (rather than function pointers)
-array references/pointers
-Member functions that operate on volative instances
-atexit() (I mention this since you're from Java)
-sizeof() applied to instances does not required parentheses
-The order of evaluation for ternary operators

[Edited by - shuma-gorath on November 17, 2009 2:46:30 PM]
Quote:Original post by shuma-gorath
-array references

Beautiful!
template<typename T, size_t size>inline size_t array_size(T (&array)[size]){    return size;}
Quote:Original post by Way Walker
Quote:Original post by frob
Quote:Original post by DevFred
On a related note, a couple of days ago I found a bug in one of my toy functions. Does anyone else see it? :)

Pipeline-unfriendly code?

For future reference, what's the pipeline-friendlier way of writing std::reverse? The only improvement I can think of is using reverse iterators (i.e. not doing std::reverse).
Any data dependencies (real or imagined) may cause a pipeline stall.

Assume the string or a segment of the string is on a single cache line. The CPU must read the line and get the value, then write the value to the cache line. It must then stall until the write is complete before the next value can be read.

This means: To avoid pipeline stalls, do not write data adjacent to read data.






The actual "best" method depends on the length of the string and where it is kept. For a string on the stack, it is fastest to put another string/array on the stack, iterate over it once, and send back the copy. Google has lots of papers detailing the performance of string reversal.

The use of c-style strings forces (at least) two iterations. Both in-place and copy version require one pass to find the length of the buffer, and a second pass to do the reversal. If you know the string length directly (as a string class) you can do it in a single pass.


It is nitpicky because string reversal is probably not a bottleneck.

But it is also good to know, since you should generally write the fastest code with the least maintenance; pick fast algorithms over slow ones.

In this case, reverse in place is both slower and less intuitive.

However, the std::reverse must work on any object, including those where there are performance concerns on copying and destroying, and potentially size concerns for large objects. In that situation, the in-place method would be preferred.
A few things about undefined behavior:
Firstly, the standard allows a compiler to provide implementation specific behavior in cases of undefined behavior. A prime example is that if you're writing an operating system, dereferencing a null pointer may actually make sense.

Now, on that note, the behavior is still undefined with regards to the C++ language, no matter how much implementation defined slag you drop on it.

Undefined behavior does not have to manifest its self as crashes, compiler errors, or anything of the sort.

The example my quiz used was std::cout<<a<<a++<<--a;. Why is this behavior undefined? If you look at it, it looks like it should be:
std::operator<<(a).operator<<(a++).operator(--a);, and it is. However, it is important to note something very important about parameters: The order of evaluation of function parameters is unspecified. As such they can be evaluated in ANY ORDER prior to the function being called. So, the following are all valid evaluations:
//the one you probably expectedstd::cout.operator<<(a);std::cout.operator<<(a++);std::cout.operator<<(--a);a;a++;--a;std::cout.operator<<(a);std::cout.operator<<(a);std::cout.operator<<(a);a++;a;--a;std::cout.operator<<(a);std::cout.operator<<(a);std::cout.operator<<(a);//and so on.

The point is: there are no real sequence points there that prevent a from being modified more than once between the function calls. Which is what ultimately causes the behavior to be undefined. If we had used a, b, and c then the behavior would have been perfectly well defined, although the order in which a b and c are evaluated would still be unspecified.

This is the danger of undefined behavior, it causes things to operate in ways that you don't expect or things will work as you expect until you change compiler versions, etc.

It should be noted that in C++0x they have tried to clean up a lot of the danger spots in the language, although how many of those changes will actually make it into the final standard is still unknown.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

This topic is closed to new replies.

Advertisement