CISC 3142 — Class Definitions

CISC 3142
Programming Paradigms in C++
Class-Related Declarations and Definitions

Declaration vs Definition

We've discussed this to some extent: a declaration is simply an announcement of the existence of a class, variable, or function, while a definition provides the details (the set of data members and member functions for classes, a possible initial value for a variable, and the function body for a function). When specifying a class, there are several alternatives as to where to place the various components of the class definition (i.e., the class declaration + the member function definitions).

While a declaration may be repeated without an effect, there can typically be only one definition:

For illustrative purposes, we'll focus on a class named Simple with a single int data member named val, a default constructor, and a single member function name getVal.

Class Declarations and Definitions

Class Declarations

A class declaration is nothing than an announcement of the existence of a particularly named class:
class Simple;
As with function declarations, the main purpose of this is to allow forward (and recursive) class declarations. We will have occasion to use them later, especially in the context of operator overloading.

Class Definition

A class definition — as mentioned above — is the specification of the data members and member function declarations:
class Simple {
public:
	Simple(int val);
	int getVal();
private:
	int val;						// data members are part of class declaration
};			

Function Definitions within the Class Definition in the Header (.h) File

// Simple.h

class Simple {
public:
	Simple(int val) : val(val) {}		// outside val is data member; inside val is parameter
	int getVal() {return val;}		// inline function
private:
	int val;						// data members are part of class declaration
};			
Notes

The Issue with Definitions in a Header File

We mentioned in the Sample Programs that the implementation (.cpp file should include the corresponding header file for consistency of function declarations/headers. The header file would also typically be included in other implementation files that use its facilities. We thus typically have the following situation:
//simple.cpp

#include "simple.h"

…
and
simple_app.cpp

#include "simple.h"

This multiple-inclusion of the same header file occurs even within the same file … as an example, almost all applications use strings and thus would contain a #include <string>. Suppose the app also was using a set of custom string utility function (other than the ones included in the standard C++ string library. Those functions would take string arguments and as the function declarations in the corresponding header file (let's call it custom_string_utils would make reference to the string class type, it would need a #include in its header file:

// app.cpp

#include <string>
#include "custom_string_utils"

…

// custom_string_utils.h

#include <string>

void pretty_print(string s);
…

When the preprocessor exapands the source file, the header file string is included twice (once from the app's include and the other from the custom_string_utils' herader file's include.

If there were variable and function definitions in the header file they would trigger multiple-definition errors at link-time.

Function Definitions Separated from the Class Declarations

As mentioned above, we may want to omit function definitions from the class definition
// Simple.h (interface/header) file
class Simple {
public:
	Simple(int val);			// headers only
	int getVal();
private:
	int val;						// data members are part of class declaration
};			
//Simple.cpp (implementation/source) file

Simple::Simple() : val(val) {}
int Simple::getVal() {return val;}

Mixtures of Non-inline and Inline Member Functions

As of C++ 11, data members may be initialized at the point of declaration:
class Simple {
	…
	int val = 0;
};

static (Class) Members

The Danger of using namespace in Header Files

Recall that the using statement allows names from the specified namespace to be used without :: qualification.

The Notion of 'Who' is Compiling / 'Ownership' of the Source (.cpp) File

As mentioned, the compiler is restricted to look at the contexts of the single .cpp file being processed; unlike Java, it does not go out looking for other files. The only external files it uses are those #include'd within the source file (one can specify when invoking the compiler which directories it should search for those files, but again, the files themselves are completely specified within the text of the source file.

As such, all files included in the source file (recursively) and expanded by the preprocessor are essentially 'guests' of the source file and should not do anything that affects the semantics of that source file. In particular, no header (.h) file should contain a using namespace; doing so would 'pollute' the namespace of the source file from that point onwards (and most likely without the knowledge of the creator of the source file).

For example, the following header file contains a using. All subsequent appearances of string then resolve to the string type of the std namespace.

// somefile.h

using namespace std;

void f(string);

A source file that includes this file will ';silently be resolving string to std::string

//app.cpp

#include "somefile.h"

// no using in .cppstring s;		//std::string by virtue of the header's <>code>using

Instead, the header files should fully qualify any names as necessary:

// somefile.h

void f(std::string);

(This is true for actual code contained in inline functions as well)

Code Used in this Lecture