This project will illustrate the features of C++ and projects that you have learned in this chapter. The project will use several source files so that you can see the effect of dependencies and how the build tool will manage changes to the source files. The project is simple: it will ask you to type your first name, and then it will print your name and the time and date to the command line.
Writing a simple project
The project structure
The project uses three functions: the main function, which calls two functions print_name and print_time. These are in three separate source files and, since the main function will call the other two functions in other source files, this means the main source file will have to have prototypes of those functions. In this example, that means a header for each of those files. The project will also use a precompiled header, which means a source file and a header file. In total, this means three headers and four source files will be used.
Creating the precompiled header
The code will use the C++ Standard Library to input and output via streams, so it will use the <iostream> header. The code will use the C++ string type to handle input, so it will use the <string> header. Finally, it accesses the C runtime time and date functions, so the code will use the <ctime> header. These are all standard headers that will not change while you develop the project, so they are good candidates for precompiling.
In Visual Studio create a C++ header file and add the following lines:
#include <iostream>
#include <string>
#include <ctime>
Save the file as utils.h.
Now create a C++ source file and add a single line to include the header file you just created:
#include ″utils.h″
Save this as utils.cpp. You will need to create a makefile for the project, so in the New File dialog, select Text File as your file type. Add the following rules for building the precompiled header:
utils.pch utils.obj :: utils.cpp utils.h
cl /EHsc /c utils.cpp /Ycutils.h
Save this file as makefile. with the appended period. Since you created this file as a text file, Visual Studio will normally automatically give it an extension of txt, but since we want no extension, you need to add the period to indicate no extension. The first line says that the two files, utils.pch and utils.obj, depend on the source file and header file being specified. The second line (prefixed with a tab) tells the compiler to compile the C++ file, not to call the linker, and it tells the compiler to save the precompiled code included into utils.h. The command will create utils.pch and utils.obj, the two targets specified.
When the make utility sees that there are two targets, the default action (when a single colon is used between targets and dependencies) is to call the command once for each target (there are macros that you can use to determine which target is being built). This would mean that the same compiler command would be called twice. We do not want this behavior, because both targets are created with a single call to the command. The double colon, ::, is a work around: it tells nmake not to use the behavior of calling the command for each target. The result is that, when the make utility has called the command once, to make utils.pch, it then tries to make utils.obj but sees that it has already been made, and so realizes that it does not need to call the command again.
Now test this out. At the command line, in the folder that contains your project, type nmake.
If you do not give the name of a makefile, the program maintenance tool will automatically use a file called makefile (if you want to use a makefile with another name, use the /f switch to provide the name):
C:\Beginning_C++\Chapter_01\Code>nmake
Microsoft (R) Program Maintenance Utility Version 14.00.24210.0
Copyright (C) Microsoft Corporation. All rights reserved.
cl /EHsc /c utils.cpp /Ycutils.h
Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24210 for x86
Copyright (C) Microsoft Corporation. All rights reserved.
utils.cpp
Do a directory listing to confirm that utils.pch and utils.obj have been made.
Creating the main file
Now create a C++ source file and add the following code:
#include "utils.h"
#include "name.h"
#include "time.h"
void main()
{
print_name();
print_time();
}
Save this file as main.cpp.
The first include file is the precompiled header for the Standard Library headers. The other two files provide function prototype declarations for the two functions that are called in the main function.
You now need to add a rule for the main file to the makefile. Add the following highlighted line to the top of the file:
main.obj : main.cpp name.h time.h utils.pch
cl /EHsc /c main.cpp /Yuutils.h
utils.pch utils.obj :: utils.cpp utils.h
cl /EHsc /c utils.cpp /Ycutils.h
This new line says that the main.obj target depends on two header files: a source file and the precompiled header file, utils.pch. At this point, the main.cpp file will not compile, because the header files do not exist yet. So that we can test the makefile, create two C++ header files; in the first header file, add the function prototype:
void print_name();
Save this file as name.h. In the second header file, add the function prototype:
void print_time();
Save this file as time.h.
You can now run the make utility, which will compile only the main.cpp file. Test this out: delete all of the target files by typing del main.obj utils.obj utils.pch on the command line and then run the make utility again. This time, you'll see that the make utility compiles utils.cpp first and then compiles main.cpp. The reason for this order is because the first target is main.obj, but since this depends on utils.pch, the make tool moves to the next rule and uses this to make the precompiled header, before returning to the rule to create main.obj.
Note that you have not defined print_name nor print_time, yet the compiler does not complain. The reason is that the compiler is only creating object files, and it is the responsibility of the linker to resolve the links to the functions. The function prototypes in the header files satisfy the compiler that the function will be defined in another object file.
Using input and output streams
So far, we have seen how to output data to the console via the cout object. The Standard Library also provides a cin stream object to allow you to input values from the command line.
Create a C++ source file and add the following code:
#include "utils.h"
#include "name.h"
void print_name()
{
std::cout << "Your first name? ";
std::string name;
std::cin >> name;
std::cout << name;
}
Save this file as name.cpp.
The first include file is the precompiled header, which will include the two Standard Library headers <iostream> and <string>, so you can use types declared in those files. The first line of the function prints the string Your first name? on the console. Note that there is a space after the query, so the cursor will remain on the same line, ready for the input.
The next line declares a C++ string object variable. Strings are zero or more characters, and each character will take up memory. The string class does all the work of allocating and freeing the memory that will be used by the string. This class will be described in more detail in Chapter 8, Using the Standard Library Containers. The cin overloads the >> operator to get input from the console. When you press the Enter key the >> operator will return the characters you typed into the name variable (treating the space character as a delimiter). The function then prints out the contents of the name variable to the console without a newline.
Now add a rule for this source file to the makefile; add the following lines to the top of the file:
name.obj : name.cpp name.h utils.pch
cl /EHsc /c name.cpp /Yuutils.h
Save this file and run the make tool to confirm that it will make the name.obj target.
Using time functions
The final source file will obtain the time and print this on the console. Create a C++ source file and add the following lines:
#include "utils.h"
#include "time.h"
void print_time()
{
std::time_t now = std::time(nullptr);
std::cout << ", the time and date are "
<< std::ctime(&now) << std::endl;
}
The two functions, std::time and std::gmtime, are C functions, and std::time_t is a C type; all are available through the C++ Standard Library. The std::time function obtains the time as the number of seconds since midnight on January 1, 1970. The function returns a value of type std::time_t, which is a 64-bit integer. The function can optionally copy this value to another variable if you pass a pointer to where, in memory, the variable is stored. In this example, we do not need this facility, so we pass the C++ nullptr to the function to indicate that a copy should not be performed.
Next, we need to convert the number of seconds to a string that has the time and date in a format you can understand. This is the purpose of the std::ctime function, which takes as a parameter a pointer to the variable that holds the number of seconds. The now variable has the number of seconds, and the & operator is used to obtain the address of this variable in memory. Memory and pointers are covered in more detail in Chapter 4, Working With Memory, Arrays, and Pointers. This function returns a string, but you have not allocated any memory for this string, nor should you attempt to free the memory used by this string. The std::ctime function creates a statically allocated memory buffer, which will be used by all the code running on the current execution thread. Every time you call the std::ctime function on the same thread of execution, the memory location used will be the same, although the contents of the memory may change.
This function illustrates how important it is to check the manual to see who has responsibility for allocating and freeing memory. Chapter 4, Working With Memory, Arrays, and Pointers, goes into more detail about memory allocation.
The string returned from std::ctime is printed to the console using several calls to the put << operator to format the output.
Now add a build rule to the makefile. Add the following to the top of the file:
time.obj : time.cpp time.h utils.pch
cl /EHsc /c time.cpp /Yuutils.h
Save this file and run the make tool, and confirm that it builds the time.obj target.
Building the executable
You now have all the object files needed for your project, so the next task is to link them together. To do this, add the following line to the top of the makefile:
time_test.exe : main.obj name.obj time.obj utils.obj
link /out:$@ $**
The target here is the executable, and the dependents are the four object files. The command to build the executable calls the link tool and uses a special syntax. The $@ symbol is interpreted by the make tool as use the target, and so the /out switch will actually be /out:time_test.out. The $** symbol is interpreted by the make tool as use all the dependencies so that all the dependencies are linked.
Save this file and run the make utility. You should find that only the link tool will be called, and it will link together the object files to create the executable.
Finally, add a rule to clean the project. It is good practice to provide a mechanism to remove all of the files created by the compile process and leave the project clean, with only the source files. After the line to link the object files, add the following lines:
time_test.exe : main.obj name.obj time.obj utils.obj
link /out:$@ $**
clean : @echo Cleaning the project...
del main.obj name.obj time.obj utils.obj utils.pch
del time_test.exe
The target clean is a pseudo target: no file is actually made, and for this reason, there are no dependencies. This illustrates a feature of the make utility: if you call nmake with the name of a target, the utility will make just that target. If you do not specify a target then the utility will make the first target mentioned in the makefile, in this case time_test.exe.
The clean pseudo target has three commands. The first command prints Cleaning the project... to the console. The @ symbol here tells the make utility to run the command without printing the command to the console. The second and third commands call the command-line tool del to delete the files. Clean the project now by typing nmake clean on the command line, and confirm that the directory has just the header files, source files, and the makefile.
Testing the code
Run the make utility again so that the executable is built. On the command line, run the example by typing the time_test command. You will be asked to type your first name; do this, and press the Enter key. You should find that your name, the time, and date are printed on the console:
C:\Beginning_C++\Chapter_01>time_test
Your first name? Richard
Richard, the time and date are Tue Sep 6 19:32:23 2016
Changing the project
Now that you have the basic project structure, with a makefile you can make changes to the files and be reassured that, when the project is rebuilt, only the files that have changed will be compiled. To illustrate this, change the print_name function in name.cpp to ask for your name in a more polite way. Change the first line in the function body as highlighted here:
void print_name()
{
std::cout << "Please type your first name and press [Enter] ";
std::string name;
Save the file and then run the make utility. This time, only the name.cpp source file is compiled, and the resulting file, name.obj, is linked with the existing object files.
Now change the name.h header file and add a comment in the file:
// More polite version
void print_name();
Make the project. What do you find? This time, two source files are compiled, name.cpp and main.cpp, and they are linked with the existing object files to create the executable. To see why these two files are compiled, take a look at the dependency rules in the makefile. The only file that was changed was name.h, and this file is named in the dependency list of name.obj and main.obj, hence, these two files are rebuilt. Since these two files are in the dependency list of time_test.exe, the executable will be rebuilt, too.