There are obvious reasons to have a class for int, because int by itself does not allow for the absence of any value. Take for instance a JSON message. It can contain the definition for an object named “foo”, and an integer named “bar”, for example:
{"foo": {"bar": 0}}
Which has the meaning that “bar" is equal to 0 (zero), but if you omit “bar”, like this:
{"foo": {}}
Now it takes on the meaning that “bar” is non-existent, which is a completely different meaning and cannot be represented by int alone. In the old days, if this situation arose, some programmers would use a separate flag, or use a specific integer value to signify that the value was not supplied, or undefined, or non-existent. But whatever you call it, a better way is to have a class for integer which defines the functionality and makes it reusable and consistent.
Another case would be a database table that has an integer column added some time after it’s creation. Records that were added prior to when the new column was added will return null, meaning no value present, and records added after the column’s creation would return a value. You may need to take a different action for null value vs. 0 (zero).
So here's the beginnings of what a class for int or string might look like. But before we get to the code, let's look at the usage as that is why you would create the class in the first place, to make your life easier in the long run.
int main(int argc, char **argv) {
xString name;
xInt age;
std::cout<< "before assignment:" << std::endl;
std::cout<< "name is " << name << std::endl;
std::cout<< "age is " << age << std::endl;
// some data collection/transfer occurs
age = 32;
name = "john";
// data validation
if (name.isNull()) {
throw std::runtime_error("name was not supplied");
}
if (age.isNull()) {
throw std::runtime_error("age was not supplied");
}
// data output
std::cout<< std::endl;
std::cout<< "after assignment:" << std::endl;
std::cout<< "name is " << name << std::endl;
std::cout<< "age is " << age << std::endl;
return 0;
}
Here is the sample output from the program:
before assignment:
name is null
age is null
after assignment:
name is john
age is 32
Note that when the instance of the xInt class has not been assigned a value, the << operator automatically prints "null" instead of zero, and the same applies to xString for name. What you do here is totally up to you. For instance, you might decide to print nothing instead of printing “null”. Also, for the sake of brevity, I've hard coded the assignments. In the real world, you would be gathering/parsing data from a file or client connection, where that process would either set (or not set) the data values according to what is found in the input data. And of course, this program won't actually ever throw the runtime exceptions, but I put them there to give you a flavor of how you might throw the errors. So, one might say, well, why don't you just throw the exception in your data collection process? Well, the answer to that is, with the eXtended class variables (xInt & xString), we can write a generic, reusable, data gathering process and then just examine the data that is returned in our business logic where we can then throw appropriate errors based on what we find.
Ok, so here's the class code to go with the above main method:
#include <iostream>
#include <string>
class xInt {
private:
int _value=0;
bool _isNull=true;
public:
xInt(){}
xInt(int value) {
_value=value;
_isNull=false;
}
bool isNull(){return _isNull;}
int value() {return _value;}
void unset() {
_value=0;
_isNull=true;
}
friend std::ostream& operator<<(std::ostream& os, const xInt& i) {
if (i._isNull) {
os << "null";
} else {
os << i._value;
}
return os;
}
xInt& operator=(int value) {
_value=value;
_isNull=false;
return *this;
}
operator const int() {
return _value;
}
};
class xString {
private:
std::string _value;
bool _isNull=true;
public:
xString(){}
xString(int value) {
_value=value;
_isNull=false;
}
bool isNull() {return _isNull;}
std::string value() {return _value;}
void unset() {
_value.clear();
_isNull=true;
}
friend std::ostream& operator<<(std::ostream& os, const xString& str) {
if (str._isNull) {
os << "null";
} else {
os << str._value;
}
return os;
}
xString& operator<<(std::ostream& os) {
os << _value;
return *this;
}
xString& operator=(std::string value) {
_value.assign(value);
_isNull=false;
return *this;
}
operator const std::string() {
return _value;
}
};
Some might say, wow, that's pretty ugly compared to just saying int or string, and yes, I agree that it's pretty wordy, but remember, you only write the base class once, and then from then on, your code that you're reading and writing every day would look more like the main method that we first looked at, and that is very concise and to the point, agreed? Next you'll want to learn how to build shared libraries so you can put all these generic classes and functionality into a re-usable .dll or .so so that you're only compiling the business logic, not the entire universe. :)