Is C++ too complex?

Started by
121 comments, last by Vortez 11 years, 4 months ago

[quote name='SiCrane' timestamp='1354812511' post='5007802']
Anyways, for a less contrived example of overloading a pointer and an integer:

ostream & operator<<(int);
ostream & operator<<(const void *);
// along with half a million other member overloads and some non-member overloads



Yeah, that one is responsible for this fun piece of code:
#include <iostream>

int main() {
volatile char s[] = "Hello, world!";
std::cout << s << '\n';
}


At least recent versions of g++ give you a reasonable warning about this one.
[/quote]
WTF is this magic? My guess is that since there's no [font=courier new,courier,monospace]volatile[/font] overload it tries to find a conversion for [font=courier new,courier,monospace]s[/font] that matches one of the overloads, which happens to be [font=courier new,courier,monospace]bool[/font] (since [font=courier new,courier,monospace]s[/font] degenerates to a [font=courier new,courier,monospace]volatile [/font][font=courier new,courier,monospace]char*[/font], and since it's not null it converts to true)? Am I even close?
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
Advertisement

WTF is this magic? My guess is that since there's no [font=courier new,courier,monospace]volatile[/font] overload it tries to find a conversion for [font=courier new,courier,monospace]s[/font] that matches one of the overloads, which happens to be [font=courier new,courier,monospace]bool[/font] (since [font=courier new,courier,monospace]s[/font] degenerates to a [font=courier new,courier,monospace]volatile [/font][font=courier new,courier,monospace]char*[/font], and since it's not null it converts to true)? Am I even close?


Yes, that's exactly it. In case you still thought C++ didn't have dark corners. :)
Yeah, that one is responsible for this fun piece of code
Oh. Oh... I'm adding [font=courier new,courier,monospace]volatile[/font] to my list of keywords that should almost never, ever be used -- I think it only appears once in my entire game engine, inside a class that is just a reinvention of a C++11 standard class, meaning it doesn't need to exist.
I'd go as far as to say that if you're using the [font=courier new,courier,monospace]volatile[/font] keyword, you've probably got a bug.

[quote name='Olof Hedman' timestamp='1354783859' post='5007683']
Interesting.
The clear separation of a declaration and an implementation is one of the things I really like with C and C++.
The header file has (should have) everything I need as a _user_ of the class, and nothing more.
Makes the intention of the class very clear, and then I can "hide" the implementation in a cpp file.
Except that the header file also contains information about private member variables and methods. So you either give the client all the details about the class, or you end up using something like pimpl, which is yet another hoop to jump through that other languages have realised provides no tangible benefit.[/quote]I'm also in the camp that gets extremely bothered by the fact that in C#/Java/etc there's no separation between declaration and implementation. I really love the fact that [font=courier new,courier,monospace].h[/font] files can just show you the what without the how.

I often use PIMPL-style idioms, where I'm writing the same thing 3 times, instead of twice, but still prefer it over C# style classes... It's not like writing lines of code is actually anyone's bottleneck or main time-sink when programming anyway.

One of the styles that I like is having [font=courier new,courier,monospace]foo.h[/font] with just the stuff the user needs to see, then [font=courier new,courier,monospace]foo_impl.h[/font] with the member variables/data layout, then [font=courier new,courier,monospace]foo.cpp[/font] with the method implementations. If you're using a [font=courier new,courier,monospace]foo[/font] you just need the first, if you're creating a [font=courier new,courier,monospace]foo[/font] you need the 2nd, or if you're working on [font=courier new,courier,monospace]foo[/font] you need the 3rd.
This isn't the 80's anymore. We don't have to program in vi and cc on the command line. Realistically, managing any project of a decent size without an IDE is just making work for no reason.[/quote]On the same point, C++ IDEs have features to automatically duplicate/sync the necessary lines between [font=courier new,courier,monospace].h[/font] and [font=courier new,courier,monospace].cpp[/font] files, if you care for that kind of thing.
Header file & separation isn't a problem. I like it, the lack of it is one of many reasons for hating on so many inferior languages. It's not OCD about why can't I organize things the way I want as much as I would like to have headers available when I don't have a sylladex to tell we what every function and it's parameters are. Header/code separation makes programming easier.

Many of the advanced features of the c++ language have pretty limited use. And there is the issue that these features can take a bit of time and experience with the language to understand. The problems that exist are when these features become core components that are absolutely necessary to achieve a design, and then that design happens to be a core feature of a game or game engine or a piece of code architecture. This is where it gets to the point that every programmer on the project needs to have an advanced understanding of c++ to be able to do anything. So a solution is to avoid these features and find other simpler solutions. I don't want to hate on less advanced programmers here, but the fact of the matter is that I know a lot more about the c++ language now then I did 5 years ago, and I programmed exclusively in c++ 5 years ago and never failed to build what ever it was I wanted to build. The problem is using these features is an easy and simple alternative to building tools that extend the compilation process to generate metadata about the project to achieve the same purpose - or any other complicated task that can be achieved with the advanced features.

And this is the part where it all gets messy. The features are perfectly stable and reliable, it's not necessarily a house of cards, but the code degenerates into something not even reminiscent of what I say c++ code looks like. And use of the system, or at least users using the system comfortably without doubts as to it's quality, becomes dependent upon the relevant skill level in c++ of the programmer. It's a bit like the language itself suffers from something a lot of software suffers from, bloat. And it needs a refactor or refinement to bring it back into sanity.

The cx11 I reckon is very good, if you're using the smart pointers you can bring your class design back to what it hypothetically 'should be'. No need to worry about implementing the copy constructor or assignment operator as the new features take care of this for you. A continuation down this path of introducing features to bring things back to sanity would be welcomed by me. This is just one thing though, it seems like there should be more. And no I VILL NOT USE BOOST. Don't even. Just don't.

EDIT: -9 points for saying that advanced c++ didn't resemble simple c++, and expressing a general feeling that I don't like the inconsistency. I'm adding this:
127.0.0.1 gamedev.net
to my hosts file.

Bye everybody.
I say Code! You say Build! Code! Build! Code! Build! Can I get a woop-woop? Woop! Woop!

Oh. Oh... I'm adding [font=courier new,courier,monospace]volatile[/font] to my list of keywords that should almost never, ever be used -- I think it only appears once in my entire game engine, inside a class that is just a reinvention of a C++11 standard class, meaning it doesn't need to exist.
I'd go as far as to say that if you're using the [font=courier new,courier,monospace]volatile[/font] keyword, you've probably got a bug.

Well, [font=courier new,courier,monospace]volatile[/font] was designed specifically for allowing low-level hardware access without needing to turn off optimizations, so it still has its use in drivers and such. Of course outside of that there isn't really any use for it in a modern system.

Some people use it thinking it makes operations atomic, but the compiler is free to reorder anything around [font=courier new,courier,monospace]volatile[/font] accesses (it just isn't allowed to optimize out the accesses themselves and their order inside a function), so it fails miserably (if you want atomic access you have to use dedicated atomic code, the variable declarations won't do).
Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.
Out of curiosity, is there a specific reason why a) there isn't a conversion from [font=courier new,courier,monospace]volatile char*[/font] to [font=courier new,courier,monospace]char*[/font] (or volatile anything), and b) there aren't [font=courier new,courier,monospace]volatile[/font] overloads in the standard library?
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]
For a, pretty much the same reason that there isn't a conversion from const char * to char *, if you cast a pointer to volatile to a pointer to non-volatile, reads and writes to that location would no longer satisfy the volatile constraints. Ex: a write to a volatile memory location might get optimized out. For b, in C++98 because there wasn't much point when the standard library didn't support any form of threading. If it makes you feel any better, C++11 did add some volatile overloads to some standard library functions.
b) there aren't volatile overloads in the standard library?
As sik mentioned above, it's only useful for programming at a level where you're interacting with some hardware detail, not general use. So there's not much incentive in adding overloads to [font=courier new,courier,monospace]ostream[/font] that only driver-authors will use (or won't use).
IMHO, I actually prefer the style where you read memory into local variables before using it anyway:volatile char* data = ..
char c = *data;
cout << c;
didn't support any form of threading
I try and avoid mentioning threading and volatile in the same sentence, because too many people already think they go hand in hand. If you're using the C++11 standard library's threading primitives, you don't need volatile. You only need it when interacting with hardware designed to support multi-threaded code, such as when implementing said primitives, or if you're writing dangerous multi-threaded code that makes assumptions about the underlying hardware.
Whether or not you think they go hand in hand, almost all of those volatile overloads I mentioned are for the C++11 threading library (the rest being trait types).

I try and avoid mentioning threading and volatile in the same sentence, because too many people already think they go hand in hand. If you're using the C++11 standard library's threading primitives, you don't need volatile. You only need it when interacting with hardware designed to support multi-threaded code, such as when implementing said primitives, or if you're writing dangerous multi-threaded code that makes assumptions about the underlying hardware.

I keep hearing that, and I'm hoping you (or someone) might be able to clarify for me. Is the following C++ pseudocode (assuming C++03) bad/evil/dangerous?

// Thread function
void threadFunction(..., volatile bool* keepRunning) // "..." represents parameters I'm omitting to keep this simple and keep the focus on volatile
{
while (*keepRunning)
{
// Process a pixel in our fractal
}
}

int main()
{
volatile bool keepRunning = true;

start_thread(threadFunction, ..., &keepRunning);
start_thread(threadFunction, ..., &keepRunning);

// Main thread goes on processing UI events and sets keepRunning to false if the user has hit the Cancel button
}

As far as I understand it, there isn't anything inherently dangerous with using [font=courier new,courier,monospace]volatile[/font] in this situation (though there may be without using it), since reads/writes are atomic (of course, one thread may access it while another thread is modifying it, but this would only be catastrophic on a non-atomic operation on a non-primitive data type, AFAIU) and only one thread is writing while the others are reading. I know [font=courier new,courier,monospace]volatile[/font] doesn't make anything thread safe, but does this sufficiently prevent the compiler from optimizing your code in such a way that it would break it? Instruction reordering isn't bad in this example, as it doesn't really matter exactly where keepRunning is accessed in the loop; just so long as it is actually read.

And what C++11 data types would be appropriate here? I checked here, and as far as I can tell, a mutex and condition variable is undesirable because they require locks. Maybe futures (which I don't fully understand)?
[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

This topic is closed to new replies.

Advertisement