Jump to content

  • Log In with Google      Sign In   
  • Create Account

Ryan_001

Member Since 23 Apr 2003
Offline Last Active Today, 06:19 PM

Posts I've Made

In Topic: C++ header inclusion question

Yesterday, 12:08 AM

but when it comes to bug hunting having 1 declaration in 1 file, as opposed to 50 declaration spread over 50 files, will be far easier to manage.

 
Are we talking about the same thing?
 
Class declaration:
class Foo {
    int bar;
}
Forward declaration:
class Foo;
The only thing that needs to be "managed" across 50 files is the name of the class. And if that changes you would have to change the files were it is used either way.


I'm not trying to be argumentative here, I am truly confused at some of the responses to this post. Apart from the odd circular definition, when do you need one and not the other? I can't think of many places where I need a pointer to an object but never call or access the object in any way?

Though to more directly answer your question, I was more thinking for things like function declarations, enumerations, constants, ect...

In Topic: C++ header inclusion question

23 October 2014 - 03:03 PM

Care to elaborate how this is a bad thing? I never ever had an error directly related to forward declaration, and having depencies spread is really a good thing - now if I have to include "X.h" into "Y.h" I can be asured that Y only has the barely minimum depencies that X relies on in its declaration, mostly due to templates/typedefs. Instead of having Y.h now suddenly contain every single include that might be there in the include-chain, which gives you nothing but increased compile times. Even with #pragma once, having all header-files included in other header-files will still lead to recompilation of half your project if you make a change to the header of certain classes.


If I include "X.h" into "Y.h" I would assume both "X.h" and "Y.h" only include what they in turn need. There are going to be dependencies that are required. Whether they are forward declared or they are included is of little difference as far as compilation times are concerned, but when it comes to bug hunting having 1 declaration in 1 file, as opposed to 50 declaration spread over 50 files, will be far easier to manage.

Now what you seem to be implying is that I'm suggesting to dump every header, in every other header, in some massive 'include everything under the sun' paradigm. That is not the case. Each file should be a logical unit or module that handles one or a few related concepts. If your code base is such that manually declaring things in each file results in significantly faster compilation times than including just those headers that are needed, then the problem is with the code base.

To be honest the recommendation to 'always forward declare' and not use #include seems so absurd that I get the feeling something has been misinterpreted, and that this post has somehow gotten off tangent and we aren't actually discussing the same thing.

In Topic: C++ header inclusion question

23 October 2014 - 01:54 AM

 

I think #2 should actually read: "Only include headers in headers if you can't get away with forward declarations"

 

Hence the "Sometimes you'll need to deviate from these rules, but in the rare occasion that you do, you'll know."  It's very rare that you can't get away with forward declarations.

 

 


Short of putting all headers in one huge header file, how can #2 be accomplished?  Not trying to be a wiseguy - I'm not well-versed in C++ and would honestly like to know.

 

As madhed mentioned above, forward declarations.  If header 2 contains a "Foo" class that you need to reference in header 1, just declare class foo; at the top of header 1.  If cpp file 1 includes header 1 AND header 2, the linker will take care of it.

 

 

Its quite common to #include headers in header files.  Manual creation of forward declarations is gonna lead to lots of subtle errors as you now have your dependencies spread across many files.  Just use #pragma once at the top of every header file, include it where needed, and you won't have issues.  As far as #pragma once being non-portable, I know of no remotely modern C++ compiler that doesn't support it (http://en.wikipedia.org/wiki/Pragma_once#Portability).


In Topic: A short discussion on PODs

22 October 2014 - 03:26 PM

 

One thing you need to consider is exception guarantee's.  If a throw occurs mid copy, you need to destruct the unfinished copies before re-throwing.


Yeah. Good catch.

This is an example of why I think that exceptions are the worst part of C++ (and to a lesser extent, every other language that has them) and why you should almost always just turn them off and use a simpler error handling approach.

The extra code bloat, mental overhead, runtime overhead, inevitable bugs, and inability to write certain containers/algorithms safely with exceptions just isn't worth supporting a rather mediocre error handling facility. Fatal error reporting (asserts), monadic errors, etc. solve more problems while introducing fewer new ones.

At the very least, enforce noexcept on your move operations. Or even do so on copy; you shouldn't be copying containers or anything that needs to allocate on copy (I'd even argue strongly that containers shouldn't even have copy operations but rather only fill-from operations).

 

 

Here I'm gonna have to disagree.  I love exceptions and copying is essential for many fundamental operations/algorithms.  I do think C++'s handling of copying/movings and their interactions with exceptions is a bit problematic, but that's a conversation for another thread...


In Topic: A short discussion on PODs

22 October 2014 - 02:30 PM

std::copy, std::unitialized_copy, std::unitialized_fill, etc.

The ones for move aren't in the standard yet apparently, but I do believe they're planned additions for C++17. There's not one for destruct.

Writing them yourself is easy enough if you need them, though, assuming you don't want the full genericness that an STL implementation has to worry about (different source and destination iterator types, etc.). For instance, here's a trivial move for noexcept types usable for a vector implementation:
 

// begin and end is a range of values to move
// out points to unitialized memory that will contain the moved objects after this completes
// this will perform a copy instead of a move if move is not nothrow, as otherwise you'll
// end up with a half-moved range if an exception is thrown with no ability to recover sanely
template <typename T>
std::enable_if_t<!std::is_trivially_move_constructible_v<T>>
uninitialized_move(T* begin, T* end, T* out)
{
  while (begin != end) {
    new (out) T(std::move_if_noexcept(*begin));
    ++begin;
    ++out;
  }
}

template <typename T>
std::enable_if_t<std::is_trivially_move_constructible_v<T>>
uninitialized_move(T* begin, T* end, T* out)
{
  std::memcpy(out, begin, (end - begin) * sizeof(T));
}
You could simplify that a bit more with tag overloading.

 

 

Ahh, I was aware of those, thought there was some I was missing ;)  One thing you need to consider is exception guarantee's.  If a throw occurs mid copy, you need to destruct the unfinished copies before re-throwing.


PARTNERS