C++ Newsletter/Tutorial Issue 13
Issue #013
July, 1996
Contents
- New Language Feature - Explicit
- Introduction to Templates Part 5 - Forcing Instantiation
- Using C++ as a Better C Part 13 - Character Types and Arrays
- A Long Example of a Complete Class
NEW LANGUAGE FEATURE - EXPLICIT
In C++ it is possible to declare constructors for a class, taking a single parameter, and use those constructors for doing type conversion. For example:
class A {
public:
A(int);
};
void f(A) {}
void g()
{
A a1 = 37;
A a2 = A(47);
A a3(57);
a1 = 67;
f(77);
}
A declaration like:
A a1 = 37;
says to call the A(int) constructor to create an A object from the integer value. Such a constructor is called a "converting constructor".
However, this type of implicit conversion can be confusing, and there is a way of disabling it, using a new keyword "explicit" in the constructor declaration:
class A {
public:
explicit A(int);
};
void f(A) {}
void g()
{
A a1 = 37; // illegal
A a2 = A(47); // OK
A a3(57); // OK
a1 = 67; // illegal
f(77); // illegal
}
Using the explicit keyword, a constructor is declared to be "nonconverting", and explicit constructor syntax is required:
class A {
public:
explicit A(int);
};
void f(A) {}
void g()
{
A a1 = A(37);
A a2 = A(47);
A a3(57);
a1 = A(67);
f(A(77));
}
Note that an expression such as:
A(47)
is closely related to function-style casts supported by C++. For example:
double d = 12.34;
int i = int(d);
INTRODUCTION TO TEMPLATES PART 5 - FORCING INSTANTIATION
In previous issues we've talked about the process of template instantiation, in which template parameters are bound to actual type arguments. For example:
template <class T> class A {};
A<int> a;
At instantiation time, the template formal parameter T is assigned the type value "int".
Instantiation is done based on need -- the generated class A<int> will not be instantiated unless it has first been referenced or otherwise used.
The actual process of instantiation is done in various ways, for example during the link phase of producing an executable program. But it is possible to explicitly force instantiation to occur in a file.
For example:
template <class T> class A {
T x;
void f();
};
template <class T> void A<T>::f() {}
template class A<double>;
will force the instantiation of A<double>.
The whole area of instantiation is still in a state of flux, and this feature may not be available with your compiler.
USING C++ AS A BETTER C PART 13 - CHARACTER TYPES AND ARRAYS
There are a couple of differences in the way that ANSI C and C++ treat character constants and arrays of characters. One of these has to do with the type of a character constant. For example:
#include <stdio.h>
int main()
{
printf("%d\n", sizeof('x'));
return 0;
}
If this program is compiled as ANSI C, then the value printed will be sizeof(int), typically 2 on PCs and 4 on workstations. If the program is treated as C++, then the printed value will be sizeof(char), defined by the draft ANSI/ISO standard to be 1. So the type of a char constant in C is int, whereas the type in C++ is char. Note that it's possible to have sizeof(char) == sizeof(int) for a given machine architecture, though not very likely.
Another difference is illustrated by this example:
#include <stdio.h>
char buf[5] = "abcde";
int main()
{
printf("%s\n", buf);
return 0;
}
This is legal C, but invalid C++. The string literal requires a trailing \0 terminator, and there is not enough room in the character array for it. This is valid C, but you access the resulting array at your own risk. Without the terminating null character, a function like printf() may not work correctly, and the program may not even terminate.
A LONG EXAMPLE OF A COMPLETE CLASS
As a means of illustrating what an actual large and complete C++ class looks like, we will present a class for managing calendar dates. Commentary on this class is given below the source.
First of all, the header:
// Date class header file
#ifndef __DATE_H__
#define __DATE_H__
typedef unsigned short Drep; // internal storage format
const int MIN_YEAR = 1875;
const int MAX_YEAR = 2025;
const Drep MAX_DAY = 55152;
const int DOW_MIN = 6;
class Date {
Drep d; // actual date
static int init_flag; // init flag
static int isleap(int); // leap year?
static Drep cdays[MAX_YEAR-MIN_YEAR+1]; // cumul days per yr
static void init_date(); // initialize date
static Drep mdy_to_d(int, int, int); // m/d/y --> day
static void d_to_mdy(Drep,int&,int&,int&);// day --> m/d/y
public:
Date(Drep); // constructor from internal
Date(const Date&); // copy constructor
Date(int, int, int); // constructor from m/d/y
Date(const char*); // constructor from char*
operator Drep(); // conversion to Drep
void print(char* = (char*)0); // print
void get_mdy(int&, int&, int&); // get m/d/y
long operator-(const Date&); // difference of dates
int dow(); // day of week
long wdays(const Date&); // work days between dates
};
#endif
and then the source itself, along with a driver program:
// Date class and driver program
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#include "date.h"
// days in the various months
const char days_in_month[12] = {31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31};
Drep Date::cdays[MAX_YEAR - MIN_YEAR + 1];
int Date::init_flag = 0;
// initialize date structures
void Date::init_date()
{
int i;
Drep cumul = 0;
init_flag = 1;
for (i = MIN_YEAR; i <= MAX_YEAR; i++) {
cumul += 365 + isleap(i);
cdays[i - MIN_YEAR] = cumul;
}
}
// a leap year?
int Date::isleap(int year)
{
if (year % 4)
return 0;
if (year % 100)
return 1;
if (year % 400)
return 0;
return 1;
}
// convert m/d/y to internal date
Drep Date::mdy_to_d(int month, int day, int year)
{
int i;
Drep d;
assert(month >= 1 && month <= 12);
assert(day >= 1);
assert(year >= MIN_YEAR && year <= MAX_YEAR);
assert(day <= days_in_month[month - 1] ||
(day == 29 && month == 2 && isleap(year)));
if (!init_flag)
init_date();
d = (year > MIN_YEAR ? cdays[year - MIN_YEAR - 1] : 0);
for (i = 1; i < month; i++)
d += days_in_month[i - 1] + (i == 2 && isleap(year));
d += day;
return d;
}
// convert internal date to m/d/y
void Date::d_to_mdy(Drep d, int& month, int& day, int& year)
{
int i;
Drep t;
if (!init_flag)
init_date();
for (i = MIN_YEAR; i <= MAX_YEAR; i++) {
if (d <= cdays[i - MIN_YEAR])
break;
}
assert(i <= MAX_YEAR);
if (i > MIN_YEAR)
d -= cdays[i - MIN_YEAR - 1];
year = i;
for (i = 1; i <= 12; i++) {
if (d <= (t = days_in_month[i - 1] +
(i == 2 && isleap(year))))
break;
d -= t;
}
assert(i <= 12);
month = i;
day = d;
}
// constructor from a Drep
Date::Date(Drep dt)
{
assert(dt <= MAX_DAY);
d = dt;
}
#define ISDEL(c) ((c) == ',' || (c) == '-' || (c) == '/')
static const char* mon[12] = {
"jan",
"feb",
"mar",
"apr",
"may",
"jun",
"jul",
"aug",
"sep",
"oct",
"nov",
"dec"
};
// constructor from a char* string
Date::Date(const char* s)
{
char buf[3][25];
int i;
int j;
int mo;
int dy;
int yr;
assert(s && *s);
// break into fields
i = 0;
for (;;) {
if (i == 3)
break;
while (*s && (*s <= ' ' || ISDEL(*s)))
s++;
if (!*s)
break;
j = 0;
if (isdigit(*s)) {
while (isdigit(*s))
buf[i][j++] = *s++;
buf[i][j] = 0;
i++;
}
else if (isalpha(*s)) {
while (isalpha(*s))
buf[i][j++] = tolower(*s++);
buf[i][j] = 0;
i++;
}
else {
break;
}
}
assert(i == 3);
// month
i = 0;
if (isalpha(buf[1][0]))
i = 1;
if (isalpha(buf[i][0])) {
if (buf[i][3])
buf[i][3] = 0;
for (j = 0; j < 12; j++) {
if (!strcmp(buf[i], mon[j]))
break;
}
j++;
mo = j;
}
else {
mo = atoi(buf[i]);
}
// day
i = !i;
dy = atoi(buf[i]);
// year
yr = atoi(buf[2]);
if (yr < 100)
yr += 1900;
d = mdy_to_d(mo, dy, yr);
}
// copy constructor
Date::Date(const Date& x)
{
d = x.d;
}
// constructor from m/d/y
Date::Date(int month, int day, int year)
{
d = mdy_to_d(month, day, year);
}
// conversion operator to Drep
Date::operator Drep()
{
return d;
}
// print a date
void Date::print(char* s)
{
int month;
int day;
int year;
char buf[25];
char* t;
d_to_mdy(d, month, day, year);
t = (s ? s : buf);
sprintf(t, "%02d/%02d/%4d", month, day, year);
if (!s)
printf("%s", t);
}
// get m/d/y from internal
void Date::get_mdy(int& mo, int& dy, int& yr)
{
d_to_mdy(d, mo, dy, yr);
}
// difference in days of two dates
long Date::operator-(const Date& dt)
{
return long(d) - long(dt.d);
}
// day of week
int Date::dow()
{
Drep dw = (d - 1) % 7 + DOW_MIN;
if (dw > 7)
dw -= 7;
return dw;
}
// working days between two dates
long Date::wdays(const Date& dt)
{
long n;
Drep i;
int mo, dy, yr;
int dw;
assert(d <= dt.d);
n = 0;
for (i = d; i <= dt.d; i++) {
Date x(i);
dw = x.dow();
if (dw == 1) // sunday
continue;
if (dw == 7) // saturday
continue;
x.get_mdy(mo, dy, yr);
if (mo == 5 && dy >= 25 && dw == 2) // memorial
continue;
if (mo == 9 && dy <= 7 && dw == 2) // labor
continue;
if (mo == 11 && dy >= 22 &&
dy <= 28 && dw == 5) // thanks
continue;
if (mo == 1 && dy == 1) // new years
continue;
if (mo == 12 && dy == 31 && dw == 6)
continue;
if (mo == 1 && dy == 2 && dw == 2)
continue;
if (mo == 7 && dy == 4) // 4th july
continue;
if (mo == 7 && dy == 3 && dw == 6)
continue;
if (mo == 7 && dy == 5 && dw == 2)
continue;
if (mo == 12 && dy == 25) // christmas
continue;
if (mo == 12 && dy == 24 && dw == 6)
continue;
if (mo == 12 && dy == 26 && dw == 2)
continue;
n++;
}
return n;
}
#ifdef DRIVER
int main()
{
char buf[25];
for (;;) {
printf("date 1: ");
gets(buf);
Date d1(buf);
printf("date 2: ");
gets(buf);
Date d2(buf);
printf("calendar days = %ld\n", d2 - d1);
printf("work days = %ld\n\n", d1.wdays(d2));
}
return 0;
}
#endif
- This class represents calendar dates for the years 1875 to 2025. An actual date is stored as an absolute day number with January 1, 1875 as the basis. There are other ways of storing dates, for example by representing the month/day/year as integers.
- The header file uses an include guard __DATE_H__ so that it can be included multiple times without error. It's common in large programming projects to have headers included more than once.
- The Date class uses a set of private static utility functions, for example one that determines if a given year is a leap year or not. These functions are private to the class but do not operate on object instances of the class.
- There are a set of constructors used to build Date objects. One of these is a copy constructor and two others are used to create Date objects from a month/day/year set of numbers, or from a string which has the date formatted in one of several forms:
- September 25, 1956
- 9/25/56
- 9 25 56
- This particular constructor will be confused by dates written in the European format, for example:
- 25/9/56
- There are member functions for determining what day of the week a given date is (Sunday - Saturday), and for computing the number of days between two dates.
- There is also a member function for computing the number of work days between two dates (inclusive of beginning and end dates). This function is somewhat arbitrary and encodes rules used in the United States, including boundary holidays (for example, if New Year's is on a Sunday, Monday will be taken as a holiday).
- The functions for turning month/day/year into an internal number, and vice versa, use a precomputed vector that gives the cumulative days since 1875 for a given year. Given this vector, the approach is straightforward and brute force.
- The day of week calculation uses modulo arithmetic, based on a known day of week for January 1, 1875.
- There are various other ways of handling dates. For example, the UNIX system represents time as the number of seconds since midnight UTC on January 1, 1970. For file timestamps and so on, a date system with a granularity of a whole day would not work. As another example, the western world changed its calendar system in September of 1752, and the above code would not work across this boundary, even if the Drep representation would handle the number of days involved.
ACKNOWLEDGEMENTS
Thanks to Nathan Myers, Eric Nagler, David Nelson, Terry Rudd, Jonathan Schilling, and Clay Wilson for help with proofreading.
SUBSCRIPTION INFORMATION / BACK ISSUES
To subscribe to the newsletter, send mail to majordomo@world.std.com with this line as its message body:
subscribe c_plus_plus
Back issues are available via FTP from:
rmii.com /pub2/glenm/newslett
or on the Web at:
There is also a Java newsletter. To subscribe to it, say:
subscribe java_letter
using the same majordomo@world.std.com address.
-------------------------
Copyright (c) 1996 Glen McCluskey. All Rights Reserved.
This newsletter may be further distributed provided that it is copied in its entirety, including the newsletter number at the top and the copyright and contact information at the bottom.
Glen McCluskey & Associates
Professional C++ Consulting
Internet: glenm@glenmccl.com
Phone: (800) 722-1613 or (970) 490-2462
Fax: (970) 490-2463
FTP: rmii.com /pub2/glenm/newslett (for back issues)
Web: http://www.rmii.com/~glenm