C++ derives most of its language elements and function structure from the C programming language, which in turn,
inherited those things from Algol, a European language. It derives its notions of class from SmallTalk.
///////////////////////////////////////////////
// ProcessCmdLine class
// - extracts path, patterns, options, number
class ProcessCmdLine
{
public:
using Path = std::string;
using Option = int;
using Options = std::vector<Option>;
using Pattern = std::string;
using Patterns = std::vector<Pattern>;
using Number = long int;
ProcessCmdLine(
int argc, char** argv,
std::ostream& out = std::cout
);
ProcessCmdLine(
const ProcessCmdLine&
) = delete;
ProcessCmdLine& operator=(
const ProcessCmdLine&
) = delete;
bool parseError();
Path path();
void path(const Path& path);
Options options();
void option(Option op);
bool hasOption(Option op);
Patterns patterns();
void pattern(const Pattern& patt);
Number maxItems();
void maxItems(Number number);
void usage();
void showCmdLine(int argc, char** argv);
void showCmdLine();
void showPath();
void showOptions();
void showPatterns();
void showMaxItems();
private:
Path path_;
Patterns patterns_;
Options options_;
int maxItems_ = 0;
bool parseError_ = false;
std::ostream& out_;
// references can't be copied
// or be targets for assignment
};
A class is a language construct that binds together member functions and data, providing access to users only to
public member functions, not to its data. This encapsulation enables the class to make strong guarantees about
validity of its data.
The class, shown on the left, is used to parse the command line of its process, to extract user-supplied information.
It has methods, like path, option(s), pattern(s), and maxItems, that allow it's state to be observed and
edited.
Note that two methods, the copy constructor and assignment operator, are declared delete. That's done
because its data member, std::ostream& out_, is not copyable, nor can it be the target of an assignment.
You can see the entire source code in Code Utilities.
Method definitions for ParseCmdLine are provided, inline, below the class declaration shown here.
That is done so that packages that need its functionality only have to include its header file.
The implementation file is used only for construction testing and demonstration.
A Value Type is simply a type that can be copied and be the target of an assignment,
e.g., it provides copying, assignment, and move member operations that allow
instances of the type to behave like built-in types.
C++ value types can be polymorphic,
providing virtual functions, and can be part of an inheritance hierarcy. For managed
languages, like C# and Java, value types are simpler, non-polymorphic, types.
In those languages, all Classes create reference types, which can be polymorphic, but
whose instances can, in general, not be copied or assigned. Only their handles can
be copied and assigned, resulting in two handles pointing to the same instance.
/////////////////////////////////////////////////
// Converter class
// - supports converting unspecified types
// to and from strings
// - a type is convertible if it provides
// insertion and extraction operators
template <typename T>
class Converter
{
public:
static std::string toString(const T& t);
static T toValue(const std::string& src);
};
//----< convert t to a string >------------
template <typename T>
std::string Converter<T>::toString(const T& t)
{
std::ostringstream out;
out << t;
return out.str();
}
//----< convert string to instance of T >--
/*
* - the string must have been generated by
* Converter<T>::toString(const T& t)
* - T::operator>> must be the inverse
* of T::operator<<
*/
template<typename T>
T Converter<T>::toValue(const std::string& src)
{
std::istringstream in(src);
T t;
in >> t;
return t;
}
Templates are compile time constructs that allow us to avoid writing a lot of nearly identical code for
classes that could sensibly use any of several concrete types, perhaps as arguments to member functions or as instances
of member data. Containers are a good example.
Templates are also useful for building functions that accept callable objects, i.e., function pointers, functors,
or lambdas. We use template functions to accept instances of any of these distinct types. Thread constructors are
a good example.
A derived class inherits all it's base's members. Non-virtual base functions should not be redefined in the
derived class. Virtual member functions may be redefined in the derived class. Pure virtual functions
must be redefined.
Composition:
Instances declared as class data members are composed by the class, creating a strong owning relationship.
Composed members are always constructed
when the composer is constructed, and destroyed when the composer is destroyed.
Aggregation:
A class aggregates a data member when it holds a pointer to the member instance on the native heap.
Creation of an instance of some type as local data in a member function is also considered to be
aggregation. Aggregated instances are owned by the aggregator, but this is a weaker relationship
than composition, as the aggregated instances do not exist until code in the aggregator creates them.
Using:
A class uses an instance of some other class when it is passed the instance to one of its public member
funtions by reference. Using is a non-owning relationship and your classes should
respect the owning code by doing nothing to invalidate the used instance. Passing an instance by value
to a member function is really aggregation, because the receiving class creates and uses its own copy.
These four relationships are all that are needed to build Object Oriented programs.
Compound objects are instances of classes that use inheritance, composition, and aggregation of other classes
to carry out their mission. We need to be careful, especially with initialization, to ensure they operate
as expected. Here is a good example of the initialization and use
of compound objects.
A functor is a class that defines operator(). That means that instances can be "invoked" like this:
class AFunctor {
public:
AFunctor(const X& x) : x_(x) {}
void operator()(const Y& y) { /* do something useful to x_, using y */ }
X value() { return x_; }
private:
X x_;
};
AFunctor func;
Y y;
func(y); // syntactically looks like a function call
func.operator(y); // equivalent to the statement above
A lambda is an anonymous callable object that can be passed to, and returned from other functions. A lambda is
really a short-cut to define a functor. A very useful feature of lambdas is their capture semantics. They
can store data defined in their local scope, to be used later when they are invoked.
std::function makeLambda(const std::string& msg)
{
std::function<void()> fun = [=]() // [=] implies capture by value,
{ // [&] implies capture by reference
std::cout << "\n displaying captured data: " << msg;
};
return fun;
}
// in some other scope
std::function myFun = makeLambda("hello CSE687-OnLine");
std::cout << "\n using Lambda: " << myFun() << "\n";
A callable object is any C++ construct that can be invoked using parentheses, callable(...).
Function pointers, functors, and lambdas are all callable objects.
Here's a function that accepts and invokes any callable object that takes no arguments:
template<typename T>
void invoker(T& callable) // callable could be a lamda with captured data
{
callable(); // use captured data instead of function arguments
}
Exceptions are a language defined mechanism for handling errors. Exception handling uses instances of
std::exception and the key words throw,
try, and catch. You should always throw exceptions by value and catch them by reference.
/////////////////////////////////////////////////////////////////////
// TestExecutor class
// - supports execution of callable objects for testing in the
// context of a try-catch block.
template<typename T>
class TestExecutor
{
public:
bool execute(T t, const std::string& name, std::ostream& out = std::cout);
private:
void check(bool result, std::ostream& out);
};
//----< execute tests in the context of a try-catch block >----------
template <typename T>
bool TestExecutor<T>::execute(T t, const std::string& name, std::ostream& out)
{
bool result = false;
try
{
result = t();
check(result, out);
out << " -- \"" << name << "\"\n";
}
catch (std::exception& ex)
{
check(false, out);
out << " -- \"" << name << "\" ";
out << ex.what() << "\n";
}
return result;
}
//----< display test results >---------------------------------------
template<typename T>
void TestExecutor<T>::check(bool result, std::ostream& out)
{
if (result)
out << " passed";
else
out << " failed";
}
The code, above, is part of a TestUtilities package.
Exception handling ensures that testing continues, even if one of the tests throws an exception in the execute
method, where each test in a sequence of tests, runs. That's
important when testing large code bases.
A namespace defines a compile-time scope used to distinguish between two or more type or function names
with the same value but that have distinct definitions in separate places in code for a single compilation unit.
A namespace extends the name of a type, for example, by prepending the type name with the namespace name, i.e.,
MyNamespace::MyType.
Namespaces can also be used to provide semantic grouping for bodies of code. Each namespace represents a group
of packages that are related to the namespace name, e.g., Utilities, FileSystem, iostream, threads, etc.
The .Net Framework is an excellent example of semantic grouping with namespaces. You might wish to open the
Object Browser in Visual Studio to explore the the .Net System libraries and think about the way they use namespaces.
That only works for managed language projects, like those for C#, because the Object Browser uses .Net reflection
to capture the information it displays.
The C++ language has a surprisingly short list of dark corners, e.g., compilable constructs that may cause
subtle errors. Most cause Liskov Substitution to fail by improper overloading, overriding,
or failing to provide virtual destructors.
A block of processing instructions, defined by a function passed to the operating system (OS), that executes
in a processor core, and is started and stopped by the OS.
A thread often runs in an environment containing many other threads that are sequenced in short time-slices
by the OS to behave like concurrent processing. A thread may run continuously in a core if there are no other
threads contending for that resource.
The C++ Standard Library provides the libraries: thread
and atomic to support concurrency.
An interface for network communication provided by a low-level library. The sockets we discuss are all stream-oriented,
reading and writing sequences of bytes. Stream-oriented means that sockets do not provide you with a message structure.
If you need that you will have to provide it.
In order to communicate these sockets have to be connected.
To connect, your code needs, on one end of the channel, a connecter socket and, on the other end, a socket listener,
to listen for, and establish a connected channel.
You will find that the Sockets library, in our repository, provides good support
for building programs using network communication.
The standard C++ libraries do not provide any support for Graphical User Interfaces. But it is fairly easy to provide
one for a native C++ application by using a C#-based Windows Presentation Foundation (WPF) project that communicates with
the application through a C++\CLI shim. The WPF-Interop demo is a simple example
of how to do this.
A .Net managed language that runs in a Common Language Runtime (CLR) virtual machine and stores its instances
in a managed heap, providing garbage collection, exception handling, and reflection services.
C++\CLI code interoperates directly with native C++ code. C++\CLI code and native C++ may be placed in the same file.
The C++ language uses a very well engineered set of standard libraries for I/O, managing data, and using threads, and much more.
Each new C++ standard introduces new libraries or new library features.
We've focused on these:
A collection of libraries for stream-based I/O, for the console, files, and in-memory strings.
Streams use the insertion operator<<(...) and extraction operator>
>(...) to build composable input and output operations.
Here is a brief presentation about the structure of the streams library.
Almost all of the capabilities of the streams library are demonstrated here:
The Standard Template Library is a subset of the C++ standard libraries. It provides a large set of containers,
each with a defined iterator type, and a set of algorithms designed to operate on the containers. All of the
containers are endowed with correct construction, copy, and destruction semantics. Here is a presentation of
the structure and top-level properties of the STL.
The Threads library, introduced in the C++11 standardization, contains classes for threads, locks of various kinds,
atomics, condition variables, and futures. Futures are interesting because they support returning
values computed by child threads, blocking until the result is ready. Here are some useful code demos and
resources for programs that use threads:
These libraries are code that I've written, and hosted on the college server. They supplement the C++ Standard Libraries,
providing functionality that does not exist there (as of C++11).
A library for managing and queriny Windows directories and their objects. It was written entirely in C++11,
using the Win32 API. It has classes File, FileInfo, Directory, and Path, that are modeled after the very
well-engineered .Net System.IO library.
A library that provides an abstraction above the Windows sockets library, making many of its operations more user
friendly. It provides classes: Socket, SocketConnecter, SocketListener, and SocketSystem.
.Net and Java properties provide simple access to class data, without compromising encapsulation. This is a small
library designed to provide equivalent functionality. It useful, but also interesting because it illustrates that
the C++ language can easily be extended using only features of C++.