Chapter 7. Uninitialized Variables

Various errors can occur when adding variables to complex classes and using them as arguments. This chapter shows you a simple way to avoid such errors.

Initialized Numbers (int, double, etc.)

Imagine that you have a class named MyClass with several constructors. Suppose you’ve decided to add some new data member named int_data_ to the private section of this class:

class MyClass {
 public:
  MyClass()
  : int_data_(0)
  {}

  explicit MyClass(const Apple& apple)
  : int_data_(0)
  {}

  MyClass(const string& some_text, double weight)
  : int_data_(0), some_text_(some_text)
  {}


 private:
  int int_data_;
  std::string some_text_;
};

When adding the new data member, you have a lot of work to do. Every time you add a new data member of a built-in type, do not forget to initialize it in every constructor like this: int_data_(0). But wait! If you read the Preface to this book, you probably remember that we are not supposed to say “Every time you do A, don’t forget to do B.” Indeed, this is an error-prone approach. If you forget to initialize this data member, it will most likely fill with garbage that would depend on the previous history of the computer and the application, and will create strange and hard-to-reproduce behavior. So what should we do to prevent such problems?

Before we answer this question, let’s first discuss why it’s only relevant for built-in types. Let’s take a look at the data member some_text_, which is of the type std::string. When you add a data member some_text_ to the class MyClass, you do not necessarily need to add its initialization to every constructor of MyClass, because if you don’t do it, the default constructor of the std::string will be called for you automatically by the compiler and will initialize the some_text_ to a reproducible state (in this case, an empty string). But the built-in types do not have constructors—that’s the problem. Therefore, the solution is simple: for class data members, do not use built-in types, use classes:

  • Instead of int, use Int

  • Instead of unsigned, use Unsigned

  • Instead of double, use Double

and so on. The complete source code of these classes can be found in Appendix F in the file named scpp_types.hpp. Let’s take a look. The core of this code is the template class TNumber:

template <typename T>
class TNumber {
 public:
  TNumber(const T& x=0)
    : data_(x)
  {}

  operator T () const { return data_; }

  TNumber& operator = (const T& x) {
    data_ = x;
    return *this;
  }

  // postfix operator x++
  TNumber operator ++ (int) {
    TNumber<T> copy(*this);
    ++data_;
    return copy;
  }

  // prefix operator ++x
  TNumber& operator ++ () {
    ++data_;
    return *this;
  }

  TNumber& operator += (T x) {
    data_ += x;
    return *this;
  }

  TNumber& operator -= (T x) {
    data_ -= x;
    return *this;
  }

  TNumber& operator *= (T x) {
    data_ *= x;
    return *this;
  }

  TNumber& operator /= (T x) {
    SCPP_TEST_ASSERT(x!=0, "Attempt to divide by 0");
    data_ /= x;
    return *this;
  }

  T operator / (T x) {
    SCPP_TEST_ASSERT(x!=0, "Attempt to divide by 0");
    return data_ / x;
  }

 private:
  T data_;
};

First of all, the constructor taking type T (where T is any built-in type, e.g., int, double, float, etc.) is not declared with the keyword explicit. This is intentional. The next function defined in the class is operator T (), which allows an implicit conversion of an instance of this class back into its corresponding built-in type. This class is intentionally designed to make it easy to convert the built-in types into it and back. It defines several common operators that you would expect to use with a built-in numeric type.

And finally, here are the definitions of actual types we can use:

typedef    TNumber<int>         Int;
typedef    TNumber<unsigned>    Unsigned;
typedef    TNumber<int64>       Int64;
typedef    TNumber<unsigned64>  Unsigned64;
typedef    TNumber<float>       Float;
typedef    TNumber<double>      Double;
typedef    TNumber<char>        Char;

How do you use these new types, such as Int and Double, with names that look like built-in types but start with uppercase letters? All these types work exactly the same way as the corresponding built-in types with one difference: they each have a default constructor, and it initializes them to zero. As a result, in the example of the class MyClass you can write:

class MyClass{
 public:
  MyClass()
  {}

  explicit MyClass(const Apple& apple)
  {}

  MyClass(const string& some_text, double weight)
  : some_text_(some_text)
  {}

 private:
  Int int_data_;
  std::string some_text_;
};

The variable int_data_ here is declared as Int, with an uppercase first letter, not int, and as a result you don’t have to put an initialization of it in all the constructors. It will be automatically initialized to zero.

Actually, there is one more difference: when you use built-in types, an attempt to divide by zero can lead to different consequences depending on the compiler and OS. In our case, for the sake of consistency, this runtime error will lead to a call to the same error handler function as we’ve used for other errors, so that you can debug on error (see Chapter 15).

Note

Robust code should not refer to variables before initializing them, but it is considered a good practice to have a safe value such as 0 instead of garbage in an uninitialized variable in case the code does refer to it.

Uninitialized Boolean

But haven’t we forgotten one more built-in type specific to C++— type bool (i.e., Boolean)? No, it is just a special case, because for a Boolean we do not need operators such as ++. Instead, we need specifically Boolean operators, such as &= and |=, so this type is defined separately:

class Bool {
 public:
  Bool(bool x=false)
  : data_(x)
  {}

  operator bool () const { return data_; }

  Bool& operator = (bool x) {
    data_ = x;
    return *this;
  }

  Bool& operator &= (bool x) {
    data_ &= x;
    return *this;
  }

  Bool& operator |= (bool x) {
    data_ |= x;
    return *this;
  }

 private:
  bool data_;
};

inline
std::ostream& operator << (std::ostream& os, Bool b) {
  if(b)
    os << "True";
  else
    os << "False";
  return os;
}

Again, as with the other classes wrapping built-in types, the type Bool (uppercase) behaves exactly like bool (the original built-in type), with two exceptions:

  • It is initialized to false.

  • It has a << operator that prints False and True instead of 0 and 1, which leads to much clearer, human-readable messages.

Why is it initialized to false, not to true? Maybe because the author is a pessimist, but you can easily follow the pattern and create a new class like BoolOptimistic that is initialized by default to true.

The only thing that we have yet to initialize is a pointer, which naturally should be initialized by default to NULL. We’ll deal with this later in Chapter 9.

So far, the motivation for using classes Int, Unsigned, Double, etc., instead of the corresponding lowercase built-in types was that you can skip initialization in multiple constructors. If you use them more widely, say, for passing arguments to the functions, here is what will to happen. Suppose you have a function taking an unsigned (the built-in one):

void SomeFunctionTaking_unsigned(unsigned u);

then the following will compile:

int i = 0;
SomeFunctionTaking_unsigned(i);

Not so with the classes we’ve discussed. If we have a function:

void SomeFunctionTakingUnsigned(Unsigned u);

then the following does not compile:

Int i = 0;
SomeFunctionTakingUnsigned(i);

Therefore, in this case, you get additional type safety at compile time for free.

Rules for this chapter to avoid uninitialized variables, especially data members of a class:

  • Do not use built-in types such as int, unsigned, double, bool, etc., for class data members. Instead, use Int, Unsigned, Double, Bool, etc., because you will not need to initialize them in constructors.

  • Use these new classes instead of built-in types for passing parameters to functions, to get additional type safety.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.60.158