Moby Disk Consulting
Software Development, Training & Consulting
William Garrison - mobydisk at mobydisk daht com

C++ tips

These are solutions to miscellaneous problems I've encountered.

  1. Namespaces
  2. MSVC7 Compiler Settings
  3. Proper #ifdef's when porting

C++ Tip #1: Namespaces

Scenario

There exists a long inconvenient namespace "SuperLibrary::NiftyStuff" which contains a useful type named "UsefulType." The author wishes to create a class outside of the "SuperLibrary" namespace, that uses this "UsefulType."

Problem

  1. It is very inconvenient to use the fully qualified name in the header.
  2. It is inappropriate to place a "using namespace SuperLibrary::NiftyStuff;" line into a header since it pollutes the namespace of anyone including the header.
#include "SuperLibrary.h"

using namespace SuperLibrary::NiftyStuff;         // Bad
using SuperLibrary::NiftyStuff:UsefulType1;       // Less bad
using SuperLibrary::NiftyStuff:UsefulType2;

class MyClass
{
private:
   SuperLibrary::NiftyStuff::UsefulType1  thing1; // Ouch!
   SuperLibrary::NiftyStuff::UsefulType2  thing2;
};

Solution 1

The author can issue the "using namespace" command in the header without interfering with other libraries by placing the command inside an unnamed namespace. Unfortunately, MSVC (others???) have issues with unnamed namespaces;

#include "SuperLibrary.h"

namespace {

using namespace SuperLibrary::NiftyStuff;         // Doesn't pollute the global namespace

class MyClass
{
private:
   UsefulType1  thing1;                           // Convenient naming
   UsefulType2  thing2;
};

} // unnamed namespace

Solution 2

Use a temporary namespace. This also provides additional protection against name collisions.

#include "SuperLibrary.h"

namespace MyClassNamespace {

using namespace SuperLibrary::NiftyStuff;	  // Doesn't pollute the global namespace

class MyClass
{
private:
   UsefulType1  thing1;                           // Convenient naming
   UsefulType2  thing2;
};

} // MyClassNamespace namespace

using namespace MyClassNamespace;

Solution 3

Pimpl your class to hide the use of "UsefulType" if possible. If you don't know what a pimpl is (or need help removing some) check out the Guru of the week.

C++ Tip #2: MSVC7 Compiler Settings

Microsoft Visual C++ has many pages of options for C/C++ projects. While the defaults are reasonable, there are several settings changed that I recommend every developer do as soon as they create the project:

Debug Information Format: C7

Location: Configuration Propertes - C/C++ - General
Resolves: "Cannot find VC70.pdb" error

This setting determines which type of debugging information is created when you build in debug mode. If you are building a .LIB or a .DLL, C7 debugging format may save you pain. The default value is "Program Database."

The default setting will build a VC70.PDB file along with the .LIB or .DLL. This file has all the debugging information including line numbers, the location of the source code, and symbolic information. If you copy the library (.LIB or .DLL) to another location, you must also copy the PDB file. But what if you have multiple libraries in one folder? In that case, the VC70.PDB files overwrite each other, and you cannot debug into the libraries. Further, the main application also has a vc70.pdb file, so it quickly becomes confusing to both the programmer and the compiler(!) which VC70.PDB is which.

C7 is a standard debugging format that can be read by multiple debuggers (in the odd case that you want to use another debugger.) But most importantly, it writes the debug information inside the .LIB or .DLL file. This is not a good thing in release mode since it bloats the library file size.

Runtime Library: Multithreaded

Location: Configuration Propertes - C/C++ - Code Generation
Resolves: Problems with linking to many libraries and threading bugs

Everyone should make sure to build with multithreaded libraries. The performance benefits do not warrant the potential compatibility issues when linking with multiple libraries.

All the libraries your program links to must use the same library: you cannot mix multi-threaded and single-threaded libraries. Any application that runs with the single-thread library works with the multi-threaded library, with perhaps some loss of speed.

Suppose you create a library that does not need to be multi-threaded, so you link to the single-threaded library. This works fine until someone must use your library AND a library that is multi-threaded. Now they are stuck. They cannot use your library any longer, and they cannot change the other one to be single-threaded! It would be nicer for everyone to standardize on using one or the other. It is impossible to standardize on the single-threaded library. Instead, we should standardize on the multi-threaded libraries.

When creating an application you can use the single-threaded library for efficiency only when you know all of the libraries you link too also use the single-threaded library. I personally choose not to worry about it, and use the multi-threaded library at all times.

Force conformance in for scoping: True

Location: Configuration Propertes - C/C++ - Language
Resolves: Problems with code compiling in other compilers and future compilers

Suppose the following code:

for (int counter=0; counter<10; counter++)
   printf("Counter is %d\n", counter);

What is the value of the variable "counter" outside of the loop?

This is a trick question: The C++ standard states that, the variable "counter" is scoped to the for statement. Thus, the variable does not exist and has no value. Microsoft Visual C++ version 6 and earlier do not follow this rule (in their defence, the C++ standard has been updated since then). By default, MSVC7 will attempt to deduce what the author expected to happen, and will compile the code either way without reporting an error. This leads to code that may not compile on other compilers, or in future versions of MSVC. Microsoft should at least issue a warning about this, but it does not. Enabling this setting will cause MSVC to report an error if the variable "counter" is used outside of the for statement. Much like using new C++ style casts, this is a good habit to get into.

Disable Language Extensions: True

Location: Configuration Propertes - C/C++ - Language
Resolves: Problems with code compiling in other compilers and future compilers

This setting disables certain minor nice things that MSVC does. The Visual Studio help describes exactly what language extensions exist. It is a good idea not to rely on these, lest your code not work on other compilers or future versions of the same compiler.

Proper #ifdef's when porting

OS != Compiler

Sometimes, when writing applications that target both POSIX/GNU operating systems and proprietary operating systems, it is necessary to wrap sections of platform-specific code in #ifdef / #endif statements.

Do not confuse the compiler with the operating system. They are not the same! For example, under Microsoft Windows, I can compile with mingw/gcc or with Microsoft Visual C++ (MSVC). When writing code that is platform- specific, or compiler-specific, be sure you check the appropriate #define!

Example 1: Misuse of #ifdef _WIN32

// If we are on Windows, disable some stupid MSVC warnings
#ifdef _WIN32
#pragma warning (disable:4786)
#endif

The above is a mistake. It checks to see if you are compiling on a WIN32 platform, and if so, issues a command to the MSVC compiler. The author has forgotten that there are other compilers for WIN32. Compiling the above code with gcc/mingw on Windows may result in an error about an incorrect compiler directive. The proper way to write this code is:

// If we are on MSVC, disable some stupid MSVC warnings
#ifdef _MSC_VER
#pragma warning (disable:4786)
#endif

The above code is what the author truly intended. To check the compiler, not the OS.

Example 2: Another misuse of #ifdef _WIN32

// I need a 64-bit integer
#ifdef _WIN32
   typedef __int64 int64_t	// Define it from MSVC's internal type
#else
   #include <stdint.h>		// Use the C99 official header
#endif

Here, the author has recognized that MSVC does not ship the stdint.h header, part of the C99 standard. Thus, they added a check to provide an equivalent type when necessary. But mingw defines _WIN32 but does not provide the MSVC __int64 type. Again, the correct code is shown below follows:

// I need a 64-bit integer
#ifdef _MSC_VER
   typedef __int64 int64_t	// Define it from MSVC's internal type
#else
   #include <stdint.h>		// Use the C99 official header
#endif

Example 3: Proper use of #ifdef _WIN32

Finally, let's see an appropriate use of the _WIN32 define.

// Take a nap
#ifdef _WIN32
   Sleep(/* whatever ... */);
#else
   nanosleep(/* whatever */);
#endif

The above checks the platform, not the compiler. This is because the Sleep() function exists in any compiler targeting the Microsoft Windows Platform. So we use the _WIN32 flag to determine if we should call the Windows Sleep() function, or the POSIX nanosleep() function. This code will compile properly on gcc/mingw or MSVC.