Jump to content

  • Log In with Google      Sign In   
  • Create Account


Is C++ too complex?


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
122 replies to this topic

#81 Cornstalks   Crossbones+   -  Reputation: 6974

Like
1Likes
Like

Posted 06 December 2012 - 02:45 PM


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.

WTF is this magic? My guess is that since there's no volatile overload it tries to find a conversion for s that matches one of the overloads, which happens to be bool (since s degenerates to a volatile char*, and since it's not null it converts to true)? Am I even close?
[ 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 ]

Sponsor:

#82 Álvaro   Crossbones+   -  Reputation: 12504

Like
3Likes
Like

Posted 06 December 2012 - 03:04 PM

WTF is this magic? My guess is that since there's no volatile overload it tries to find a conversion for s that matches one of the overloads, which happens to be bool (since s degenerates to a volatile char*, 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. :)

#83 Hodgman   Moderators   -  Reputation: 28636

Like
2Likes
Like

Posted 06 December 2012 - 08:42 PM

Yeah, that one is responsible for this fun piece of code

Oh. Oh... I'm adding volatile 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 volatile keyword, you've probably got a bug.


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.

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 .h 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 foo.h with just the stuff the user needs to see, then foo_impl.h with the member variables/data layout, then foo.cpp with the method implementations. If you're using a foo you just need the first, if you're creating a foo you need the 2nd, or if you're working on foo 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.

On the same point, C++ IDEs have features to automatically duplicate/sync the necessary lines between .h and .cpp files, if you care for that kind of thing.

Edited by Hodgman, 06 December 2012 - 08:47 PM.


#84 Kyall   Members   -  Reputation: 287

Like
-2Likes
Like

Posted 07 December 2012 - 07:05 AM

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.

Edited by Kyall, 08 December 2012 - 12:36 AM.

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

#85 Sik_the_hedgehog   Crossbones+   -  Reputation: 1549

Like
2Likes
Like

Posted 07 December 2012 - 07:11 AM

Oh. Oh... I'm adding volatile 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 volatile keyword, you've probably got a bug.

Well, volatile 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 volatile 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.

#86 Cornstalks   Crossbones+   -  Reputation: 6974

Like
0Likes
Like

Posted 07 December 2012 - 12:24 PM

Out of curiosity, is there a specific reason why a) there isn't a conversion from volatile char* to char* (or volatile anything), and b) there aren't volatile overloads in the standard library?
[ 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 ]

#87 SiCrane   Moderators   -  Reputation: 9498

Like
2Likes
Like

Posted 07 December 2012 - 01:18 PM

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.

#88 Hodgman   Moderators   -  Reputation: 28636

Like
2Likes
Like

Posted 07 December 2012 - 06:40 PM

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 ostream 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.

Edited by Hodgman, 07 December 2012 - 06:43 PM.


#89 SiCrane   Moderators   -  Reputation: 9498

Like
1Likes
Like

Posted 07 December 2012 - 07:31 PM

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).

#90 Cornstalks   Crossbones+   -  Reputation: 6974

Like
0Likes
Like

Posted 07 December 2012 - 07:51 PM

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 volatile 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 volatile 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)?
[ 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 ]

#91 Hodgman   Moderators   -  Reputation: 28636

Like
2Likes
Like

Posted 07 December 2012 - 08:33 PM

Is the following C++ pseudocode (assuming C++03) bad/evil/dangerous? ... Instruction reordering isn't bad in this example

That's probably the most common/acceptable use of volatile -- telling the compiler that it should definitely read that boolean each iteration, instead of optimising it to a single read -- especially in cases where reordering isn't a concern.
This is only something that works in practice though, and is reliant on assumptions about your hardware. There's no requirement in C++03 that when one thread writes a value of 'true' to the boolean, that this value will ever become visible to other threads, volatile or not.

since reads/writes are atomic

This is another hardware-specific detail, not specified by C++03.

And what C++11 data types would be appropriate here?

In your case, atomic and memory_order_relaxed. Deciding that locks are too slow though is an optimisation issue.

One thing that bugs me is that MSVC's volatile has acted like C++11's atomic (with memory_order_seq_cst) since VS2005 -- i.e. on x86, it uses cmpxchg-type instructions. This is because too many people wrote bad volatile-based code that should be wrong due to re-ordering issues, so Microsoft changed the meaning of volatile to include a full memory fence (no read/write can be reordered past a volatile read/write), to fix people's buggy code, which just encourages people to write more buggy code that will break on other C++ compilers...

With your loop, using MSVC's volatile or C++11's atomic, you get one fully-fenced read per iteration. Using locks, and assuming no contention (Posted Image) you get a fenced read, a regular read, and a fenced write per iteration, which isn't much different. Taking contention into account though, you also might get a busy-wait with repeated fenced reads, and possibly a context-switch.
Aside from these performance differences, there's sometimes theoretical reasons to want a particular kind of non-blocking guarantee, which is a better reason to avoid locks. N.B. some kinds of lock-free systems will have worse performance than locking ones, but do so because they require the guarantee for whatever reason.

almost all of those volatile overloads I mentioned are for the C++11 threading library

Are there any valid use-cases for volatile in multi-threaded code, aside from ones like the above that can be replaced with atomic?

Edited by Hodgman, 07 December 2012 - 08:39 PM.


#92 SiCrane   Moderators   -  Reputation: 9498

Like
1Likes
Like

Posted 07 December 2012 - 09:27 PM

I'm not the one who put in those overloads. The standards committee put in the volatile overloads only for threading library. If you've got a problem with threading and volatile being associated by the C++ standard library, you'll have to take it up with them. I'm just reporting the fact that they are.

#93 Hodgman   Moderators   -  Reputation: 28636

Like
1Likes
Like

Posted 07 December 2012 - 09:50 PM


Are there any valid use-cases for volatile in multi-threaded code, aside from ones like the above that can be replaced with atomic?

I'm not the one who put in those overloads.

That was a genuine bit of curiosity on my part. If the standards committee has done this, then there must be some use for it... but in my experience atomic solves most of them. I was hoping someone could teach me a genuine situation where we'd need volatile in threaded code.

#94 e‍dd   Members   -  Reputation: 2105

Like
0Likes
Like

Posted 07 December 2012 - 10:36 PM

And what C++11 data types would be appropriate here?

In your case, atomic and memory_order_relaxed.


Would memory_order_acquire/memory_order_release be more appropriate? I guess you would want the two threads to agree on the order of writes.

#95 ChaosEngine   Crossbones+   -  Reputation: 2248

Like
0Likes
Like

Posted 09 December 2012 - 02:14 PM

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 .h files can just show you the what without the how.


You could argue that the separation between declaration and implementation is built into the language in C# and java using interfaces, as opposed to C++s frankly demented compilation model.

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.


No but the best code is the code you don't have to write. I'm not saying it's a huge burden, but it's just unnecessary.

A C# interface is a much cleaner expression of intent than a header file
if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

#96 Hodgman   Moderators   -  Reputation: 28636

Like
6Likes
Like

Posted 11 December 2012 - 06:00 AM

Someone just down-voted all my posts in this thread about volatile -- I'd appreciate being told why I'm wrong as well as that I am wrong, so I (and others) can learn from my mistake.


To explain why I cringe at the sight of volatile in C++ code that isn't interacting with special hardware registers or memory mappings:

Volatile is so often misunderstood that some code-bases check for it in commit logs and automatically flag it as a bug.
See: http://kernel.org/do...red-harmful.txt
See also the "double checked locking is broken" fiasco.

Volatile isn't required in threaded code if you're already using synchronisation primitives like mutexes or semaphores.

Carnegie Mellon teach in their secure coding materials, not to use volatile as a synchronisation primitive, because it's not required to work as one.

It's taught that when using POSIX threads, you shouldn't ever use volatile, because pthreads' synchronisation primitives make it unnecessary.

HP/IBM employees opine to remind us that volatile isn't required if using synchronisation primitives, that volatile variables aren't necessarily atomic, and that the actual details are always platform specific anyway.

When it's used as a synchronisation primitive in threaded code, it's often incorrect, for example:
int data = 42;
volatile bool ready = false;
void Thread1()
{
  data = 100;
  ready = true;
}
void Thread2()
{
  while(!ready){Sleep();}
  printf("%d", data );
}
The above code will print 100 on a 2005+ Microsoft compiler, but is not required to do so according to the C++ standard. A compliant C++ compiler will likely print 100 or 42, but could print anything. The write of "ready = true" is allowed to become visible to other threads before the write of "data = 100" does. N.B. even if you make data volatile, the order that the changes become visible in Thread2 is still unspecified.

You can't correctly implement the above synchronisation using just volatile. Maybe this works on your compiler and your CPU, but it's not required to. Because of this, if you're writing multi-threaded code that uses volatile to synchronise data between threads, you've likely got a bug (maybe not on your current platform if you're lucky).

In order to correctly implement thread synchronisation in the above example, you need to be aware of the memory fences required on your target hardware (or use C++11's atomic and memory ordering functionality); volatile doesn't address these issues at all... Well, on MSVC it does (MSVC inserts full fences around volatile accesses, well beyond what is required by the spec), which is very bad IMHO, because this causes people write incorrect code which happens to work until they port it to another platform...

To paraphrase Herb Sutter, if you're looking for volatile in C++, you probably really want atomic.

Edited by Hodgman, 11 December 2012 - 07:57 AM.


#97 Yrjö P.   Crossbones+   -  Reputation: 1412

Like
0Likes
Like

Posted 11 December 2012 - 06:46 AM

You could argue that the separation between declaration and implementation is built into the language in C# and java using interfaces, as opposed to C++s frankly demented compilation model.

A C# interface is a much cleaner expression of intent than a header file

If you make a pure virtual class in C++, how is that not a clean expression of intent?

#98 Bubsy   Members   -  Reputation: 407

Like
1Likes
Like

Posted 11 December 2012 - 12:06 PM

You misinterpreted ChaosEngine.

It's not about the C++ runtime model (on which the pure virtual class acts like an interface indeed), what he criticized was the source code allegedly separation between declaration (header files) and implementation (.cpp files), which IMO violates the DRY principle. More so, AFAIK the compiler and the preprocessor don't enforce this source code separation, so it's sintatically legal to write code in the header files.

#99 Álvaro   Crossbones+   -  Reputation: 12504

Like
2Likes
Like

Posted 11 December 2012 - 12:09 PM

Not only is it syntactically legal to write code in the header files: Templated code has to go there.

#100 Bubsy   Members   -  Reputation: 407

Like
3Likes
Like

Posted 11 December 2012 - 12:26 PM

Yeah, totally forgot about the templates.

So one could argue that the header/source-code model fail to provide a clear separation between declaration and implementation, becoming (taking in account only this feature) ultimatelly a hindrance to the programmer, that must keep two files in sync instead of only one.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS