C++ Tips: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
Line 15: Line 15:
== Standards ==
== Standards ==


* Use <tt>SG_LOG</tt> calls instead of <cout> and iostreams.
* Use <tt>SG_LOG</tt> calls instead of <cout> and iostreams. SG_LOG is controllable (partially so right now, moreso in the future) and the overhead of messages can be avoided at build time. <iostreams> can be expensive both at compile-time and run-time (not always, but if used naively).


* Use <tt>SGGeod</tt> instead of basic longitude/latitude/elevation doubles. It's easier to pass to other methods, type safe (can't confuse order of lat/lon) and unit-safe (degrees vs radians vs meters vs feet is explicit in the API).
* Use <tt>SGGeod</tt> instead of basic longitude/latitude/elevation doubles. It's easier to pass to other methods, type safe (can't confuse order of lat/lon) and unit-safe (degrees vs radians vs meters vs feet is explicit in the API).

Revision as of 22:19, 7 October 2009

This page collects various tips and common pitfalls when working with the C++ code. Some are generic, others are FlightGear specific

Primitive Types

  • Use the SimGear primitive types for quaternions, vectors and matrices - the PLIB ones are deprecated (and PLIB will be removed at some point in the future). In some places it makes sense to use OSG types directly - that decision is left to the developer to decide which makes more sense.

Formatting and Style

Note FlightGear is full of different code styles and formats - the best guidance is to follow the code you're working in, unless it is obviously broken.

  • Prefer early-return style. Don't make people scroll to see which code-path they're inside; use return to manage control flow, instead of deep nesting of if clauses. If you are nesting more than three level deep, consider making a helper function for the inner levels, or see if you can invert the logic to reduce indentation.
  • Use exceptions. Exception support is not well developed, but they provide a clean way to handle genuinely unusual situations without (easily ignorable) bool or integer return codes. The main loop catches exceptions, so use the SimGear exception, provide good location and message strings when you throw, and all should be well.

Standards

  • Use SG_LOG calls instead of <cout> and iostreams. SG_LOG is controllable (partially so right now, moreso in the future) and the overhead of messages can be avoided at build time. <iostreams> can be expensive both at compile-time and run-time (not always, but if used naively).
  • Use SGGeod instead of basic longitude/latitude/elevation doubles. It's easier to pass to other methods, type safe (can't confuse order of lat/lon) and unit-safe (degrees vs radians vs meters vs feet is explicit in the API).
  • Never pollute the namespace in a header file, via using declarations, even for standard library types. I.e don't be lazy and write using std::string; in a header - each time you refer to string, vector, etc, you need to fully qualify the name with std::. It's a bit more type, but much safer for code that includes the header file. (In source files, you are welcome to do as you please)

C++ Efficiency

Rather than duplicate the many fine works in this area, it's likely you should find and read 'Effective-C++', 'More Effective-C++' and 'Effective STL', all by Scott Meyers. The most critical points are repeated here, though.

  • Always pass aggregate types by (const) reference, especially STL containers, string or mathematical tuples such as SGQuat or SGVec3. String copying in particular is common and leads to many wasteful malloc and free calls. Passing STL containers by value is extremely wasteful, as is returning them by value - pass them into methods by a reference, and out the same way. This avoids any container copying at all, hopefully.
  • Prefer forward declaration to includes. Some things require pulling in a header, but in general headers should pull in everything the need to compile independently, and nothing more. Passing aggregate types by reference helps, since then a forward declaration suffices to refer to them. Avoid pulling in standard library headers (or system headers) into if you can avoid it, and never pull iostream into a header - use iosfwd, which exists for the purpose.
  • The compiler is almost certainly smarter about inlining than you are. One line methods can live in header files, sometimes. Anything more should live in an implementation file. Otherwise, compile times increase for each compilation unit that includes the header. (Obviously templates are an exception to this)

Pitfalls

  • Watch out for confusing geocentric coordinates (SGGeoc) and geodetic coordinates (SGGeod). If you use the SG types, the correct conversions are enforced, but many places in the code create a geocentric type from geodetic lat/lon. Geocentric computations are less expensive to compute, but there are very few places where the performance difference will be noticeable.
  • Magnetic vs true headings, bearings and courses - watch out for whether any bearing values are true or magnetic. Most user-facing properties are magnetic, since that's what is used in charts, runway headings, etc. Internal computations (SGGeodesy::direct, etc) work in true bearings, so conversion is required. The magvar code computes magnetic variation for you, but also note the variation depends on position - if you read the variation from the property tree, you're getting the value at the user's latitude / longitude.
  • Units - both imperial vs metric, elevation vs altitude, ground speed vs indicated airspeed, track vs heading, degrees vs radians, and so on. Naming can help, as can using types such as SGGeod to manage data. Many bugs can creep in by confusing similar but different values.
  • When the simulation is paused, SGSubsystem::update is still called, but with dt = 0. This can easily lead to divide-by-zero errors in code; check for it explicitly if working on subsystem code that is doing time-dependency calculations.