首先考虑一个具有几个构造函数的MyClass类。假设我们决定在这个类的私有部分添加一个新的数据成员,称为int_data_:
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_;
};
添加这个新的数据成员时,需要做大量的工作。每次添加一个内置类型的数据成员时,不要忘记在每个构造函数中对它进行初始化(采用int_data_(0)这样的形式)。事实上,这是一种容易出错的方法。如果忘了对这个数据成员进行初始化,它很可能填充了垃圾信息,具体取决于计算机和应用程序以前的历史,可能导致奇怪且很难复制的行为。因此,为了防止这种错误,我们应该怎么做?
首先我们讨论下这个问题是与内置类型有关的。观察std::string类型的数据成员some_text_。当我们向MyClass类添加数据成员some_text_时,并不需要在MyClass类的每个构造函数中对它进行初始化,因为std::string的默认构造函数将会被编译器自动调用,把some_text_初始化为一个可重复的状态(此例中为空字符串)。但是内置类型并没有构造函数,我们应该怎么办呢?其实很简单,对于类的数据成员,不要使用内置类型,而是使用类,如下:
- 不要使用int,改用Int
- 不要使用unsigned,改用Unsigned
- 不要使用double,改用Double
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;
}
//后缀操作符x++
TNumber operator ++ (int)
{
TNumber<T> copy(*this);
++data_;
return copy;
}
//前缀操作符++x
TNumber& operator ++ ()
{
++data_;
return *this;
}
TNumber& operator += (T x)
{
data_ += x;
return *this;
}
TNumber& operator 0= (T x)
{
data_ 0= x;
return *this;
}
TNumber& operator *= (T x)
{
data_ *= x;
return *this;
}
TNumber& operator /= (T x)
{
SCPP_TEST_ASSERT(x!=0,"Attept to divide by 0");
data_ /= x;
return *this;
}
T operator / (T x)
{
SCPP_TEST_ASSERT(x!=0,"Attept to divide by 0");
return data_ / x;
}
private:
T data_;
};
首先,接受T类型(T是任何内置类型,例如int、double、float等)的构造函数,并没有声明explicit。这是有意而为之的。这个类所声明的下一个函数是operator T(),它允许把这个类的实例隐式转换回对应的内置类型。这个类有意设计为很容易在它与内置类型之间方便地来回转换。它还定义了几个常见的操作符,它们也是使用内置的数值类型时所期望使用的。
以下是我们可以使用的实际类型的定义:
<span style="font-size:18px;">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;</span>
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_;
};
在这里,变量int_data_被声明为以大写字母开头的Int类型,而不是int。这样,我们就不需要在所有的构造函数中添加对它进行初始化的代码。它将自动被初始化为零。
实际上还有一个区别:当我们使用内置类型时,试图将它除零可能导致不同的结果,具体取决于编译器和操作系统。为了保持一致,这个运行时错误将导致调用与处理其他错误相同的错误处理函数,使我们可以对错误进行调试。
健壮的代码不应该在变量被初始化之前引用它们。但是,如果确实发生了这种情况,让未初始化的变量具有一个像零这样的安全值,显然要比具有随机的垃圾值好得多。