Getting Started With Cplusplus - Luca Hunt
Getting Started With Cplusplus - Luca Hunt
Table of Contents
The Story behind the Succinctly Series of Books
About the Author
Introduction
Preface
Chapter 1 Types
Chapter 2 Namespaces
Chapter 3 Functions and Classes
Chapter 4 Storage Duration
Chapter 5 Constructors, Destructors, and Operators
Chapter 6 Resource Acquisition is Initialization
Chapter 7 Pointers, References, and Const-Correctness
Chapter 8 Casting in C++
Chapter 9 Strings
Chapter 10 C++ Language Usages and Idioms
Chapter 11 Templates
Chapter 12 Lambda Expressions
Chapter 13 C++ Standard Library
Chapter 14 Visual Studio and C++
Detailed Table of Contents
Introduction
Introduction
C++ Succinctly was written to help professional C# developers learn modern
C++ programming. The aim of this book is to leverage your existing C# knowledge
in order to expand your skills. Whether you need to use C++ in an upcoming
project, or simply want to learn a new language (or reacquaint yourself with it),
this book will help you learn all of the fundamental pieces of C++ so you can
understand projects and samples written in C++ and begin writing your own C++
programs.
As with any large subject, there simply wasn’t room to cover everything (an
example being the new atomic features added in C++11), and others might have
decided to order the topics differently. I’m thinking particularly of pointers, a topic I
cover in depth only further into the book. They are important, so some might have
chosen to cover them earlier, but I feel you do not need to understand pointers to
understand the material that precedes their coverage; understanding the
preceding topics will make it much easier for you to understand them.
I’ve done my best to be as accurate as possible without sounding like a
language specification or documentation file. I hope I have succeeded. I consulted
the C++11 language specification frequently while writing this, and I also read
everything from StackOverflow posts, to MSDN docs, to GCC docs, and beyond.
There are areas where I intentionally simplified things. As you continue to expand
your knowledge of C++, you will undoubtedly reach issues where you need to
have a more comprehensive understanding in order to accomplish your goal or
eliminate a bug. If reading this book imparts enough knowledge—and a good-
enough feel for the language that you are able to recognize, diagnose, and
resolve those issues—then I will be content that I have succeeded in my goals.
Welcome to C++!
Preface
Preface
Trademarks, etc.
Being a retired lawyer, I feel compelled to include this brief section, which you
probably don’t care about unless you are a lawyer for one of these companies or
organizations. The remainder of this preface is much more enjoyable reading for
non-lawyers, so please don’t let this put you off. Microsoft®, Visual Studio®,
Visual C++®, Visual C#®, Windows®, Windows NT®, Win32®, MSDN®,
Silverlight®, DirectX®, and IntelliSense® are registered trademarks of Microsoft
Corporation. GNU® is a registered trademark of the Free Software Foundation.
ISO® is a registered service mark of the International Organization for
Standardization. IEC® is a registered service mark of International Engineering
Consortium, Inc. Unicode® is a registered service mark of Unicode, Inc. (a.k.a.
The Unicode Consortium). Intel® is a registered trademark of Intel Corporation.
Other trademarks and service marks are the properties of their respective owners.
Program Entry Point
In C#, the entry point for a program is a static method named Main. Often you
will not actually see it since various frameworks provide their own (e.g.,
Silverlight), but it is there, somewhere, since without it the operating system would
not know where to begin executing your program.
The entry point of a C++ program is the main function. A simple version looks
like this:
int main(int argc, char* argv[])
{
// Your program starts here.
#if !defined(_PCHAR_H)
#define _PCHAR_H 1
#endif
Second, here’s what the entry point now looks like (I have omitted the
inclusion of the header file here):
int _pmain(int /*argc*/, _pchar* /*argv*/[])
{
// Your program starts here.
Chapter 1 Types
Fundamental Types
C++ contains the same familiar keywords (e.g., int) that you recognize from
C#. This is unsurprising given that both are C-like languages. There is, however,
one potential landmine that can throw you into trouble. While C# explicitly defines
the sizes of fundamental types (a short is a 16-bit integer, an int is a 32-bit integer,
a long is a 64-bit integer, a double is a 64-bit double-precision IEEE 754 floating
point number, etc.), C++ makes no such guarantees.
Implementations
Like many programming languages, there are multiple C++ implementations
from various companies and organizations. Two of the most popular C++
implementations are Visual C++ and GCC. The C++ standard leaves some of its
details to be defined by the implementer. If you are writing cross-platform code,
you need to keep these implementation-defined details in mind, lest you find a
program working fine on one OS and failing in weird ways on another.
The smallest fundamental unit in C++ is char, which only needs to be at least
large enough to hold the 96 basic characters that the C++ standard specifies, plus
any other characters in the implementation’s basic character set. In theory, some
implementation of C++ could define a char as 7 bits or 16 bits … almost anything
is possible. But in practice you don’t need to worry too much about a char being
anything other than 8 bits (the equivalent of the byte or sbyte type in C#), which is
its size in Visual C++.
In C++, char, signed char, and unsigned char are three distinct types. All three
are required to take up the same amount of storage in memory. So a char in
practice is either signed or unsigned. Whether it is signed or unsigned is
implementation defined (see the sidebar). In Visual C++ the char type is, by
default, signed. But you can use a compiler switch to have it treated as unsigned
instead. In GCC, whether it is signed or unsigned depends on which CPU
architecture you are targeting.
The signed integer types, in size order from smallest to largest, are:
1. signed char
2. short int
3. int
4. long int
5. long long int
The only guarantee of the size of each of these integer types is that each one
is at least as large as the next smallest integer type. In Visual C++, an int and a
long int are both 32-bit integers. It is only the long long int that is a 64-bit integer.
Note: You can simply write long or long long; you do not need to write long int
or long long int, respectively. The same is also true for short int (i.e. you can just
write short). The short type is a 16-bit signed integer in Visual C++.
Each of the integer types has a corresponding unsigned integer type. You just
put the keyword unsigned in front to get the unsigned version (except for signed
char, which you change to unsigned char).
If you need to ensure that you are using specific sizes, you can include the
C++ Standard Library header file cstdint (e.g., #include cstdint ), which defines,
among other things, the types:
int8_t
int16_t
int32_t
int64_t
uint8_t
uint16_t
uint32_t
uint64_t
These types have their use, but you will find that most APIs do not use them;
instead, they use the fundamental types directly. This can make your
programming confusing, as you constantly need to check the underlying
fundamental type to ensure you do not end up with unintended truncation or
expansion.
These types might come into use more, so I recommend checking for their
usage in major libraries and APIs from time to time and adjusting your code
accordingly if they become widely adopted. Of course, if you absolutely need a
variable to be, for example, an unsigned 32-bit integer, you should certainly make
use of uint32_t and make any adjustments for API calls and portability as needed.
Floating-point numbers are the same as far as size order rules. They go from
float to double to long double. In Visual C++, float is a 32-bit floating-point number
and double and long double are both 64-bit floating point numbers (long double is
not larger than double, in other words).
C++ does not have any native type that is comparable to C#’s decimal type.
However, one of the nice things about C++ is there are typically a large number of
free or inexpensive libraries that you can license. For example, there’s the
decNumber library, the Intel Decimal Floating Point Math Library, and the GNU
Multiple Precision Arithmetic Library. None are exactly compatible with C#’s
decimal type, but if you are writing for Windows systems only, then you can use
the DECIMAL data type to get that compatibility if needed, along with the Decimal
Arithmetic Functions and the Data Type Conversion Functions.
There is also a Boolean type, bool, which can be true or false. In Visual C++, a
bool takes up a byte. Unlike in C#, a bool can be transformed into an integer type.
When false, it has an integer-equivalent value of 0, and when true, it has a value
of 1. So the statement bool result = true == 1; will compile and result will evaluate
to true when the statement has been executed.
Then there is the wchar_t type, which holds a wide character. A wide
character’s size varies based on the platform. On Windows platforms, it is a 16-bit
character. It is the equivalent of C#’s char type. It is frequently used to construct
strings. We will discuss strings in another chapter since many variants can be
used for strings.
Lastly, there is the void type, which is used the same way it is in C#. And there
is a std::nullptr_t type, which is messy to explain properly, but basically is there to
be the type of the nullptr literal, which is what you should use instead of NULL or a
literal 0 (zero) to check for null values.
Enumerations
Enumerations
Enumerations are fairly similar to each other in C++ and C#. C++ has two
types of enums: scoped and un-scoped.
A scoped enumeration is defined as either an enum class or an enum struct.
There is no difference between the two. An un-scoped enumeration is defined as
a plain enum. Let’s look at a sample:
Sample: EnumSampleEnumSample.cpp
#include iostream
#include ostream
#include string
#include “../pchar.h”
// You can specify any underlying integral type you want, provided it fits.
enum Flavor : unsigned short int
{
Vanilla,
Chocolate,
Strawberry,
Mint,
};
int _pmain(int /*argc*/, _pchar* /*argv*/[])
{
Flavor f = Vanilla;
f = Mint; // This is legal since the Flavor enum is an un-scoped enum.
Color c = Color::Orange;
//c = Orange; // This is illegal since the Color enum is a scoped enum.
std::wstring flavor;
std::wstring color;
switch (c)
{
case Color::Red:
color = L”Red”;
break;
case Color::Orange:
color = L”Orange”;
break;
case Color::Yellow:
color = L”Yellow”;
break;
case Color::Blue:
color = L”Blue”;
break;
case Color::Indigo:
color = L”Indigo”;
break;
case Color::Violet:
color = L”Violet”;
break;
default:
color = L”Unknown”;
break;
}
switch (f)
{
case Vanilla:
flavor = L”Vanilla”;
break;
case Chocolate:
flavor = L”Chocolate”;
break;
case Strawberry:
flavor = L”Strawberry”;
break;
case Mint:
flavor = L”Mint”;
break;
default:
break;
}
Note: In C++, the scope resolution operator is ::. We’ll discuss this in more
detail later on. For now, just think of it as serving many of the same purposes as
the . operator in C#.
This code will give the following output:
Flavor is Mint (3). Color is Orange (1).
The size of Flavor is 2.
The size of Color is 4.
As you can see in the sample, the scoped Color enumeration requires you to
access its members in the same way as C# by prefacing the enumeration
member with the enumeration’s name and the scope resolution operator. By
contrast, the un-scoped Flavor enumeration allows you simply to specify the
members without any prefix. For this reason, I think it’s better practice to prefer
scoped enumerations: You minimize the risks of naming collisions and lessen
namespace pollution.
Notice that there is another difference with scoped enumerations: When we
wanted to output the numerical value of the scoped Color enum, we had to use
the static_cast operator to convert it to an int, while we did not need to do any
casting for the un-scoped Flavor enumeration.
For the Flavor enumeration, we specified the underlying type as being an
unsigned short int. You can also specify the underlying type for scoped
enumerations. Specifying the underlying type is optional, but is mandatory if you
wish to use forward declaration with an un-scoped enumeration. Forward
declaration is a way to speed up program compile times by only telling the
compiler what it needs to know about a type rather than forcing it to compile the
whole header file the type is defined in.
We will look at this later on. For now, just remember that an un-scoped
enumeration must have its underlying type explicitly specified in order to use a
forward declaration of it; a scope enumeration does not require specification of its
underlying type to use a forward declaration of it (the underlying type will be int if
none is specified).
You can do the same thing with enumerations in C++ as you can in C# in
terms of explicitly assigning values to members, and in terms of creating flag
enumerations. You do it all the same way, except you don’t need to apply anything
like the FlagAttribute in C++ to create flag enumerations; you just assign the
correct values and proceed from there.
std::wcout, std::wcerr, std::wcin
struct Color
{
float ARGB[4];
std::wcin a;
std::wcout std::endl
L”Please enter a noun (one word, no spaces) ”
L”and then press Enter: “;
std::wstring noun;
// This uses our custom operator from above. Come back to this sample
// later when we’ve covered operator overloading and this should make
// much more sense.
std::wcout std::endl
L”Cornflower Blue is ” c L”.” std::endl;
return 0;
}
The previous code is a fairly simple demo. It has no error checking, for
instance. So, if you enter an incorrect value for the integer, it will just run through
to the end with std::wcin returning instantly without any data (that’s what it does
unless and until you resolve the error).
If you are interested in iostream programming, including using things like
std::wofstream to output data to a file and std::wifstream to read data in from a file
(they work the same as std::wcout and std::wcin, just with added functionality for
dealing with the fact that they work with files), see the MSDN iostream
programming pages. Learning all the ins and outs of streams could easily fill a
book just on its own.
One last thing though. You’ve undoubtedly noticed that the stream functionality
looks a bit odd with the bit shifting operators and . That’s because these operators
have been overloaded. While you would expect the bit shift operators to act a
certain way on integers, there isn’t any specific expectation you’re likely to have
about how they should work when applied to an output stream or an input stream,
respectively. So the C++ Standard Library streams have co-opted these operators
to use them for inputting and outputting data to streams. When we want the ability
to read in or write out a custom type that we’ve created (such as the previous
Color structure), we simply need to create an appropriate operator overload. We’ll
learn more about operator overloading later in the book, so don’t worry if it’s a bit
confusing right now.
Classes and Structures
Unions
The union type is a bit odd, but it has its uses. You will encounter it from time
to time. A union is a data structure appearing to hold many data members, but
only allowing you to use one of its data members at any one time. The end-result
is a data structure that gives you many possible uses without wasting memory.
The size of the union is required to be large enough only to contain the largest
member of the union. In practice, this means the data members overlap each
other in memory (hence, you can only use one at a time). This also means you
have no way of knowing what the active member of a union is unless you keep
track of it somehow. There are many ways you could do that, but putting a union
and an enum in a structure is a good, simple, tidy way of doing it. Here’s an
example.
Sample: UnionSampleUnionSample.cpp
#include iostream
#include ostream
#include “../pchar.h”
struct SomeData
{
SomeValueDataType Type;
union
{
int iData;
float fData;
double dData;
} Value;
SomeData(void)
{
SomeData(0);
}
SomeData(int i)
{
Type = SomeValueDataType::Int;
Value.iData = i;
}
SomeData(float f)
{
Type = SomeValueDataType::Float;
Value.fData = f;
}
SomeData(double d)
{
Type = SomeValueDataType::Double;
Value.dData = d;
}
};
typedef
The first thing to understand about typedef is that despite the implications of its
name, typedef does not create new types. It is an aliasing mechanism that can be
used for many things.
It is used a lot in implementing the C++ Standard Library and other template-
based code. This is, arguably, its most important use. We will explore it more in
the chapter on templates.
It can save you from a lot of typing (though this argument lost some of its force
with the repurposing of the auto keyword for type deduction in C++11). If you have
a particularly complicated data type, creating a typedef for it means you only need
to type it out once. If your complicated data type’s purpose is unclear, giving it a
more semantically meaningful name with a typedef can help make your program
easier to understand.
It is sometimes used as an abstraction by developers to easily change a
backing type (e.g., from a std::vector to a std::list) or the type of a parameter (e.g.,
from an int to a long). For your own internal-use-only code, this should be frowned
upon. If you are developing code that others will be using, such as a library, you
should never try to use a typedef in this way. All you are doing is decreasing the
discoverability of breaking changes to your API if you change a typedef. Use them
to add semantic context, sure, but do not use them to change an underlying type
in code that others rely on.
If you need to change the type of something, remember that any change to a
function’s parameters is a breaking change as is a change in return type or the
addition of a default argument. The proper way to handle the possibility of a future
type change is with abstract classes or with templates (whichever is more suitable
or whichever you prefer, if both will serve). This way the public interface to your
code will not change, only the implementation will. The Pimpl idiom is another
good way to keep a stable API while retaining the freedom to change
implementation details. We will explore the Pimpl idiom, short for “pointer to
implementation,” in a later chapter.
Here is a small code block illustrating the syntax for typedef.
class ExistingType;
return 0;
}
Chapter 2 Namespaces
Chapter 2 Namespaces
The Global Namespace
Consider the following code:
Sample: GlobalNamespaceSampleGlobalNamespaceSample.cpp
#include iostream
#include ostream
#include “../pchar.h”
if (true == 1)
{
std::wcout L”true == 1!” std::endl;
}
return 0;
}
C++ lets you have methods that are not part of a class. These are called
functions. More particularly, they are often called stand-alone functions since the
C++ terminology for a class method is a class member function. We will discuss
functions in much more detail in a later chapter.
In the previous sample, we define two functions, AddTwoNumbers and wmain.
These two functions are both in the global namespace. The global namespace is
the base level in which everything else within the program exists. C++, owing to
its C heritage, allows you to define anything within the global namespace (so you
can define namespaces, classes, structures, variables, functions, enums, and
templates).
C# also has the concept of a global namespace, but it does not allow you to
define anything within it other than namespaces and types. In the previous
example, we have the statement int g_x = 10; which defines an integer named
g_x within the global namespace and assigns it a value of 10. This is what is
commonly referred to as a global variable. In C# global variables are illegal.
As a brief aside, every programming language I have ever worked with has
had its share of bad features—things the language allows, but things that usually
lead to issues. These issues include hard-to-debug problems, subtle errors that
go unnoticed for a long time, maintainability problems, readability problems, and
all the other frustrating things that add many hours to development time without
any benefit. C++ is no different. When we come across something that fits this
description, I will do my best to call it out. This is one of those times.
Global variables are bad. Avoid them whenever possible. There is a common
convention when using them in C++, which is to prefix the variable name with g_,
as in the previous example. While this helps to alert you and other programmers
to the fact that this is a global variable, it does not change the fact that it is a
global variable, having all the problems I described. This isn’t a book on bad
programming practices, so I’m not going to spend time explaining why global
variables are bad. All you need to know is this feature exists in C++, but you
should avoid using it whenever possible.
The Scope Resolution Operator ‘::’
In other instances, you use either the . operator or the - operator, depending
on whether you are accessing the member directly or via a pointer.
This can seem complicated since C# just uses the . operator for all of the
purposes that ::, ., and - are used for in C++.
Note: We’ll discuss the . and - operators later. For now, you just need to know
that they are used for accessing instance member variables and non-static
member functions (which you use depending on whether or not you are working
with a pointer).
For the most part, you’ll be fine. The only place you’re likely to trip up is if you
try to access a base class member by using the . operator rather than the ::
operator, or if you try to specify an enum member using something other than ::. If
you ever compile your program and receive a syntax error complaining about
“missing ‘;’ before ‘.’ “, it’s a good bet you used a . where you should’ve used a ::
instead.
Defining Namespaces
Defining Namespaces
A namespace is defined in much the same way as it is in C#. Here is an
example:
Sample: NamespacesSampleNamespacesSample.cpp
#include iostream
#include ostream
#include string
#include “../pchar.h”
namespace Places
{
namespace Cities
{
struct City
{
City(const wchar_t* name)
{
Name = wstring(name);
}
wstring Name;
};
}
}
Note: Never include a using directive in a header file. If you do that, you don’t
just import the types and namespaces in that namespace into the header file, you
also import them into any source or header file that includes the header file. This
causes really nasty namespace pollution issues. We’ll be discussing header files
next, so anything that’s unclear about this should make sense then. Just
remember that having a using namespace directive in a header file is a bad idea;
only use them in your source code files.
Chapter 3 Functions and Classes
Note: The reason the .H suffix was left off from the C++ Standard Library
include files was to avoid naming collisions with C++ compilers that already
provided header files that used those names when the C++ Standard Library was
introduced. They are normal header files, have no fear.
To understand why the difference between declaration and definition matters in
C++, it’s important to have a basic understanding of the C++ build process. Here’s
what generally happens:
1. The preprocessor examines a source file, inserts the text of the files specified
by the include statements (and the text of the files specified by their include
statements, etc.), and also evaluates and acts on any other preprocessor
directives (e.g., expanding macros) and any pragma directives.
2. The compiler takes the output from the preprocessor and compiles that code
into machine code, which it stores, along with other information needed for
the linking phase, in an OBJ file.
3. Steps 1 and 2 are repeated for each source file within the project.
4. The linker examines the output files from the compiler and the library files that
your project links. It finds all of the places where the compiler identified
something as being declared but not defined within that particular source file.
It then locates the appropriate address for the definition and patches that
address in.
5. Once everything has been linked successfully, the linker binds everything
together and outputs the finished product (typically either an executable
program or a library file).
An error during any of those phases will stop the build process, of course, and
the previous description is only a rough sketch of the Visual C++ build chain.
Compiler authors have some flexibility in exactly how they do things. For example,
there’s no requirement that any intermediate files be produced, so in theory, the
whole build process could be done in memory, though in practice, I doubt anyone
would ever do that. So consider that list as just a rough outline, not an exact
description.
I’ve been referring to everything as source files to keep the terminology simple.
Within the C++ standard, these combinations of a source file plus all of its include
files is referred to as a compilation unit. I mention that now only because I will be
using the term a bit further along. Let’s consider the three build phases in turn.
The preprocessor doesn’t care about C++ declarations and definitions. Indeed,
it doesn’t even care if your program is in C++. The only business it has with your
source files is to take care of all lines that begin with a #, thus marking them as
preprocessor directives. As long as those lines are properly formed, and it can
find all of the included files, if any, the preprocessor will do its work, adding and
removing text as directed. It will pass the results on to the compiler, typically
without writing its result out to a file since compilation immediately follows
preprocessing.
The compiler does care about declarations and definitions and very much is
concerned with whether your program is valid C++ code or not. However, it
doesn’t need to know what a function does when it comes across it. It just needs
to know what the function signature is—such as int AddTwoNumbers(int, int);.
The same is true for classes, structures, and unions; as long as the compiler
knows the declaration (or in the case of a pointer, simply that the particular token
is a class, a structure, a union, or an enum), then it doesn’t need any definitions.
With just the declaration, it knows if your call to AddTwoNumbers is syntactically
correct and that the class Vehicle; is in fact a class, so it can create a pointer to it
when it sees Vehicle* v;, which is all it cares about.
The linker does care about definitions. Specifically, it cares that there is one,
and only one, definition matching each of the declarations in your project. The
lone exception is inline functions, which end up being created in each compilation
unit in which they are used. However, they are created in a way that avoids any
issues with multiple definitions.
You can have duplicate declarations among the compilation units for your
program; doing so is a common trick for improving build times, as long as only
one definition matches a declaration (except for inlines). In order to ensure this
one definition rule is met, C++ compilers tend to use something called name
mangling.
This ensures each declaration is matched up with its proper definition,
including issues such as overloaded functions and namespaces (which allow the
same name to be reused if the uses are in different namespaces), and class,
structure, union, and enum definitions nested within classes, structures, or unions.
This name mangling is what results in terrifying linker errors, which we will see
an example of in the “Inline Member Functions” section.
The severability of declarations from definitions lets you build your C++
projects without recompiling each source file every time. It also lets you build
projects that use libraries for which you do not have the source code. There are,
of course, other ways to accomplish those goals (C# uses a different build
process for instance). This is the way C++ does it; understanding that basic flow
helps make sense of many peculiarities in C++ that you do not encounter in C#.
Functions
Functions
There are two types of functions in C++: stand-alone functions and member
functions. The main difference between them is that a member function belongs to
a class, structure, or union, whereas a stand-alone function does not.
Stand-alone functions are the most basic types of functions. They can be
declared in namespaces, they can be overloaded, and they can be inline. Let’s
look at a few.
Sample: FunctionsSampleUtility.h
#pragma once
namespace Utility
{
inline bool IsEven(int value)
{
return (value % 2) == 0;
}
PrintIsEvenResult(i1);
PrintIsEvenResult(i2);
PrintIsEvenResult(ll1);
PrintIsEvenResult(ll2);
return 0;
}
The header file Utility.h declares and defines two inline functions, both called
IsEven (making IsEven an overloaded function). It also declares three more
functions: two called PrintIsEvenResult and one called PrintBool. The source file
Utility.cpp defines these last three functions. Lastly, the source file
FunctionsSample.cpp uses that code to create a simple program.
Any functions defined in a header file must be declared inline; otherwise, you’ll
wind up with multiple definitions and a linker error. Also, function overloads need
to be different by more than just their return type; otherwise, the compiler cannot
make sure you are really getting the version of the method you wanted. C# is the
same way, so this shouldn’t be anything new.
As seen in Utility.cpp, when you are defining a stand-alone function that is in
a namespace, you need to put the namespace before the function name and
separate it with the scope resolution operator. If you used nested namespaces,
you include the whole namespace nesting chain—for example, void
RootSpace::SubSpace::SubSubSpace::FunctionName(int param) { … };.
Simple Class
Simple Class
The following sample includes a class broken into a header file and a source
file.
Sample: SimpleClassSampleVehicleCondition.h
#pragma once
#include string
namespace Inventory
{
enum class VehicleCondition
{
Excellent = 1,
Good = 2,
Fair = 3,
Poor = 4
};
switch (condition)
{
case Inventory::VehicleCondition::Excellent:
conditionString = L”Excellent”;
break;
case Inventory::VehicleCondition::Good:
conditionString = L”Good”;
break;
case Inventory::VehicleCondition::Fair:
conditionString = L”Fair”;
break;
case Inventory::VehicleCondition::Poor:
conditionString = L”Poor”;
break;
default:
conditionString = L”Unknown Condition”;
break;
}
return conditionString;
}
}
Sample: SimpleClassSampleVehicle.h
#pragma once
#include string
namespace Inventory
{
enum class VehicleCondition;
class Vehicle
{
public:
Vehicle(
VehicleCondition condition,
double pricePaid
);
~Vehicle(void);
VehicleCondition GetVehicleCondition(void)
{
return m_condition;
};
private:
VehicleCondition m_condition;
double m_basis;
};
}
Sample: SimpleClassSampleVehicle.cpp
#include “Vehicle.h”
#include “VehicleCondition.h”
Vehicle::~Vehicle(void)
{
}
return 0;
}
In Vehicle.h, we begin with a forward declaration of the VehicleCondition
enum class. We will discuss this technique more at the end of the chapter. For
now the key points are (1) that we could either use this forward declaration or
include the VehicleCondition.h header file and (2) that the declaration of
VehicleCondition must come before the class definition for Vehicle.
In order for the compiler to allot enough space for instances of Vehicle, it
needs to know how large each data member of Vehicle is. We can let it know
either by including the appropriate header file or, in certain circumstances, by
using a forward declaration. If the declaration of VehicleCondition came after the
definition of Vehicle, then the compiler would refuse to compile the code since the
complier would not know how big VehicleCondition is or even what type of data it
is.
In that case, a simple declaration suffices to tell the compiler what
VehicleCondition is (an enum class) and how big it is. Enum classes default to
using an int as their backing field unless otherwise specified. If we leave the
backing field blank, but then say to use a short, or a long, or some other backing
field type somewhere else, the compiler would generate a different error
message, telling us we have multiple, conflicting declarations.
We then proceed to define the Vehicle class. The definition includes the
declaration of its member functions and its member variables. For the most part,
we do not define the member functions. The exceptions are the
GetVehicleCondition member function and the GetBasis member function, which
we will discuss in the “Inline Member Functions” section.
We define the other member functions of Vehicle in Vehicle.cpp. In this case,
the member functions are the constructor, the destructor, and
SetVehicleCondition. Typically, a function like SetVehicleCondition would be inline,
so would simple constructors and destructors in the Vehicle class. They are
defined separately here to illustrate how you define these types of member
functions when they are not inline functions. We will discuss the odd-looking
constructor syntax in the chapter devoted to constructors. The rest of the Vehicle
class code should be clear.
Member Functions
As discussed earlier, member functions are part of a class, structure, or union.
Simply, I will talk about them as class members from here on.
Static member functions can call other static class member functions,
regardless of protection level. Static member functions can also access static
class member data either explicitly (i.e. SomeClass::SomeFloat = 20.0f;) or
implicitly (i.e. SomeFloat = 20.0f;), regardless of protection level.
The explicit form is helpful if you have a parameter with the same name as a
class member. Prefixing member data with an m_, such as m_SomeFloat,
eliminates that problem and makes it clear when you are working with class
member data versus local variables or parameters. That’s just a style choice, not
a requirement.
Instance (i.e. non-static) member functions are automatically assigned a this
pointer to the instance data for the instance on which they were called. Instance
member functions can call other class member functions and access all class
member data either explicitly—the same as static members using this-m_count++;
for instance data—or implicitly—the same as static and instance data (e.g.,
m_data++;), regardless of protection level.
Inline Member Functions
namespace Inventory
{
enum class VehicleCondition;
class Vehicle
{
public:
Vehicle(
VehicleCondition condition,
double pricePaid
);
~Vehicle(void);
private:
VehicleCondition m_condition;
double m_basis;
};
VehicleCondition Vehicle::GetVehicleCondition(void)
{
return m_condition;
}
double Vehicle::GetBasis(void)
{
return m_basis;
}
}
As you can see, after the class declaration, we defined the member functions
we wanted inline, just as we would if they were in a source file. The key difference
is that the function declarations in the class are preceded by the inline keyword,
and the function definitions are in the header file itself. If you leave that keyword
off, you will get a linker error as in Figure 1.
Tip: Don’t try to make everything inline. You will just end up with slow compile
times that kill your productivity. Do inline things that make sense, like simple getter
and setter functions for member variables. Like anything else, profile first, and
then optimize if needed.
Protection Levels and Access Specifiers
public
protected
private
These access specifiers denote the level of accessibility that the member has.
In SampleClassVehicle.h, you can see two examples of how these are used.
Note that unlike in C#, you do not restate the access specifier in front of each
member. Instead, you state the access specifier, followed by a colon (e.g.,
public:), and then every declaration and definition that comes after is given that
level of accessibility until you reach another access specifier.
By default, class members are private. This means if you have no access
specifier at the beginning of the class declaration, then all members that are
declared will be private until an access specifier is reached. If none is reached,
you’d have an entirely private class, which would be very odd.
Structure members default to public, so on occasion you’ll see a structure
without any access specifiers. If you wish to use them in a structure, though, they
work the same as in a class.
Lastly, you can use the same access specifier more than once; if you want to
organize your class so you define the member functions first and then the
member variables (or vice versa), you could easily do something like this:
Note: This code is expository only; it is not included in any of the samples.
#include string
class SomeClass
{
public:
SomeClass(void);
virtual ~SomeClass(void);
int AddTwoInts(int, int);
void StoreAString(const wchar_t*);
private:
bool CheckForIntAdditionOverflow(int, int);
public:
int SomePublicInteger;
protected:
std::wstring m_storedString;
};
The previous class definition doesn’t define anything particularly useful.
However, it does serve as an example of the use of all three access specifiers. It
also demonstrates that you can use specifiers more than once, such as public in
the previous example.
Inheritance
Inheritance
When specifying classes that your class derives from In C++, you should also
specify an access specifier. If not, you will get the default access levels: private for
a class and public for a structure. Note that I said classes. C++ supports multiple-
inheritance. This means a class or structure can have more than one direct base
class or structure, unlike C# where a class can have only one parent.
C++ does not have a separate interface type. In general, multiple-inheritance
should be avoided except as a workaround for lack of a separate interface. In
other words, a class should have only zero or one real base class along with zero
or more purely abstract classes (interfaces). This is just a personal style
recommendation, though.
There are some good arguments for multiple-inheritance. For instance, say
you have three groups of functionality. Each one consists of functions and data.
Then say each group is unrelated to the other—there’s no connection between
them, but they aren’t mutually exclusive. In this case, you may wish to put each
functionality group into its own class. Then if you have a situation where you want
to create a class needing two of these groups, or even all three, you can simply
create a class that inherits from all three, and you’re done.
Or, you’re done as long as you didn’t have any naming conflicts in your public
and protected members’ functions and variables. For example, what if all three of
the functionality groups have a member function void PrintDiagnostics(void);?
You’d be doomed, yes? Well, it turns out that no, you are not doomed (usually).
You need to use some weird syntax to specify which base class’ PrintDiagnostics
function you want. And even then you aren’t quite done.
C++ lets you specify whether you want a class to be a plain base class or a
virtual base class. You do this by putting or not putting the keyword virtual before
the class’ name in the base class specifier. We’ll look at a sample shortly that
addresses all of this, but before we do, it’s important to understand that if you
inherit a class at least twice, and two or more of the inheritance’s are not virtual,
you will end up with multiple copies of that class’ data members
This causes a whole bunch of problems when trying to specify which of those
you wish to use. Seemingly, the solution is to derive from everything virtually, but
that has a run-time performance hit associated with it due to how C++
implementations tend to resolve virtual members. Better still, try to avoid having
this ever happen in the first place, but as that’s not always possible, do remember
virtual inheritance.
And now a sample to help make this all make sense:
Sample: InheritanceSampleInheritanceSample.cpp
#include iostream
#include ostream
#include string
#include typeinfo
#include “../pchar.h”
class A
{
public:
A(void) : SomeInt(0) { }
virtual ~A(void) { }
int SomeInt;
};
private:
float m_fValue;
};
};
class B3 : public A
{
public:
B3(void) : A() { }
virtual ~B3(void) { }
};
private:
wstring m_id;
};
virtual ~NonVirtualClass(void) { }
const wchar_t* Id(void) const { return m_id.c_str(); }
private:
wstring m_id;
};
void DemonstrateNonVirtualInheritance(void)
{
NonVirtualClass nvC = NonVirtualClass();
++nvCB1.SomeInt;
nvCB3.SomeInt += 20;
wcout
L”B1::SomeInt = ” nvCB1.SomeInt endl
L”B3::SomeInt = ” nvCB3.SomeInt endl
endl;
// Let’s see a final demo of the result. Note that the Conflict
// member function is also ambiguous because both B1 and B3 have
// a member function named Conflict with the same signature.
wcout
typeid(nvC).name() endl
nvC.Id() endl
nvC.VirtId() endl
//// This is ambiguous between B1 and B3
//nvC.Conflict() endl
// But we can solve that ambiguity.
nvC.B3::Conflict() endl
nvC.B1::Conflict() endl
//// GetSomeInt is ambiguous too.
//nvC.GetSomeInt() endl
endl
typeid(nvCB3).name() endl
nvCB3.Id() endl
nvCB3.VirtId() endl
nvCB3.Conflict() endl
endl
typeid(nvCB1).name() endl
nvCB1.Id() endl
nvCB1.VirtId() endl
nvCB1.GetSomeInt() endl
nvCB1.Conflict() endl
endl;
}
void DemonstrateVirtualInheritance(void)
{
VirtualClass vC = VirtualClass();
++vCB1.SomeInt;
vCB2.SomeInt += 20;
wcout
L”B1::SomeInt = ” vCB1.SomeInt endl
L”B3::SomeInt = ” vCB2.SomeInt endl
endl;
// Let’s see a final demo of the result. Note that the Conflict
// member function is still ambiguous because both B1 and B2 have
// a member function named Conflict with the same signature.
wcout
typeid(vC).name() endl
vC.Id() endl
vC.VirtId() endl
vC.B2::Id() endl
vC.B2::VirtId() endl
vC.B1::Id() endl
vC.B1::VirtId() endl
vC.A::Id() endl
vC.A::VirtId() endl
// This is ambiguous between B1 and B2
//vC.Conflict() endl
// But we can solve that ambiguity.
vC.B2::Conflict() endl
vC.B1::Conflict() endl
// There’s no ambiguity here because of virtual inheritance.
vC.GetSomeInt() endl
endl
typeid(vCB2).name() endl
vCB2.Id() endl
vCB2.VirtId() endl
vCB2.Conflict() endl
endl
typeid(vCB1).name() endl
vCB1.Id() endl
vCB1.VirtId() endl
vCB1.GetSomeInt() endl
vCB1.Conflict() endl
endl
typeid(vCA).name() endl
vCA.Id() endl
vCA.VirtId() endl
vCA.GetSomeInt() endl
endl;
}
Note: Many of the member functions in the previous sample are declared as
const by including the const keyword after the parameter list in the declaration.
This notation is part of the concept of const-correctness, which we will discuss
elsewhere. The only thing that the const-member-function notation means is that
the member function is not changing any member data of the class; you do not
need to worry about side effects when calling it in a multi-threading scenario. The
compiler enforces this notation so you can be sure a function you mark as const
really is const.
The previous sample demonstrates the difference between virtual member
functions and non-virtual member functions. The Id function in class A is non-
virtual while the VirtId function is virtual. The result is that when creating a base
class reference to NonVirtualClass and call Id, we receive the base class’ version
of Id, whereas when we call VirtId, we receive NonVirtualClass’s version of VirtId.
The same is true for VirtualClass, of course. Though the sample is careful to
always specify virtual and override for the overrides of VirtId (and you should be
too), as long as A::VirtId is declared as being virtual, then all derived class
methods with the same signature will be considered virtual overrides of VirtId.
The previous sample also demonstrates the diamond problem that multiple-
inheritance can produce as well as how virtual inheritance solves it. The diamond
problem moniker comes from the idea that if class Z derives from class X and
class Y, which both derive from class W, a diagram of this inheritance relationship
would look like a diamond. Without virtual inheritance, the inheritance relationship
does not actually form a diamond; instead, it forms a two-pronged fork with each
prong having its own W.
NonVirtualClass has non-virtual inheritance from B1, which has virtual
inheritance from A, and from B3, which has non-virtual inheritance from A. This
results in a diamond problem, with two copies of the A class’ member data
becoming a part of NonVirtualClass’ member data. The
DemonstrateNonVirtualInheritance function shows the problems that result from
this and also shows the syntax used to resolve which A you want when you need
to use one of A’s members.
VirtualClass has virtual inheritance from both B1, which has virtual inheritance
from A, and from B2, which also has virtual inheritance from A. Since all the
inheritance chains that go from VirtualClass to A are virtual, there is only one copy
of A’s data; thus, the diamond problem is avoided. The
DemonstrateVirtualInheritance function shows this.
Even with virtual inheritance, VirtualClass still has one ambiguity. B1::Conflict
and B2::Conflict both have the same name and same parameters (none, in this
case), so it is impossible to resolve which one you want without using the base-
class-specifier syntax.
Naming is very important when dealing with multiple-inheritance if you wish to
avoid ambiguity. There is, however, a way to resolve ambiguity. The two
commented-out using declarations in NonVirtualClass demonstrate this resolution
mechanism. If we decided we wanted to always resolve an ambiguity in a certain
way, the using declaration lets us do that.
Note: The using declaration is useful for resolving ambiguity outside of a class
too (within a namespace or a function, for instance). It is also useful if you wish to
bring only certain types from a namespace into scope without bringing the entire
namespace into scope with a using namespace directive. It is okay to use a using
declaration within a header, provided it is inside a class, structure, union, or
function definition, since using declarations are limited to the scope in which they
exist. You should not use them outside of these since you would be bringing that
type into scope within the global namespace or within whatever namespace you
were in.
One thing I did not touch on in the sample is inheritance access specifiers
other than public. If you wanted, you could write something like class B : protected
class A { … }. Then class A’s members would be accessible from within B’s
methods, and accessible to any class derived from B, but not publicly accessible.
You could also say class B : private class A { … }. Then class A’s members would
be accessible from within B’s methods, but not accessible to any classes derived
from B, nor would they be publicly accessible.
I mention these in passing simply because they are rarely used. You might,
nonetheless, come across them, and you may even find a use for them. If so,
remember that a class that privately inherits from a base class still has full access
to that base class; you are simply saying that no further-derived classes should
have access to the base class’ member functions and variables.
More common, you will come across mistakes where you or someone else
forgot to type public before a base class specifier, resulting in the default private
inheritance. You’ll recognize this by the slew of error messages telling you that
you can’t access private member functions or data of some base class, unless
you are writing a library and don’t test the class. In that case, you will recognize
the issue from the angry roars of your users. One more reason unit testing is a
good idea.
Abstract Classes
Abstract Classes
An abstract class has at least one pure virtual member function. The following
sample shows how to mimic a C# interface.
Sample: AbstractClassSampleIWriteData.h
#pragma once
class IWriteData
{
public:
IWriteData(void) { }
virtual ~IWriteData(void) { }
#include “IWriteData.h”
class ConsoleWriteData :
public IWriteData
{
public:
ConsoleWriteData(void) { }
virtual ~ConsoleWriteData(void) { }
void ConsoleWriteData::WriteLine(void)
{
wcout endl;
}
cwd.WriteLine(10);
r_iwd.WriteLine(14.6);
p_iwd-WriteLine(L”Hello Abstract World!”);
return 0;
}
The previous sample demonstrates how to implement an interface-style class
in C++. The IWriteData class could be inherited by a class that writes data to a log
file, to a network connection, or to any other output. By passing around a pointer
or a reference to IWriteData, you could easily switch output mechanisms.
The syntax for an abstract member function, called a pure virtual function, is
simply to add = 0 after the declaration, as in IWriteData class: void Write(int value)
= 0;. You do not need to make a class purely abstract; you can implement
member functions or include member data common to all instances of the class. If
a class has even one pure virtual function, then it is considered an abstract class.
Visual C++ provides a Microsoft-specific way to define an interface. Here’s the
equivalent of IWriteData using the Microsoft syntax:
Sample: AbstractClassSampleIWriteData.h
#pragma once
__interface IWriteData
{
virtual void Write(const wchar_t* value) = 0;
virtual void Write(double value) = 0;
virtual void Write(int value) = 0;
Forward Declarations
As we’ve discussed, when you include a header file, the preprocessor simply
takes all the code and inserts it right into the source code file that it is currently
compiling. If that header file includes other header files, then all of those come in
too.
Some header files are huge. Some include many other header files. Some are
huge and include many other header files. The result is that a lot of code can wind
up being compiled again and again simply because you include a header file in
another header file.
One way to avoid the need to include header files within other header files is to
use forward declarations. Consider the following code:
#pragma once
#include “SomeClassA.h”
#include “Flavor.h”
#include “Toppings.h”
class SomeClassB
{
public:
SomeClassB(void);
~SomeClassB(void);
int GetValueFromSomeClassA(
const SomeClassA* value
);
bool CompareTwoSomeClassAs(
const SomeClassA first,
const SomeClassA second
);
void ChooseFlavor(
Flavor flavor
);
void AddTopping(
Toppings topping
);
void RemoveTopping(
Toppings topping
);
private:
Toppings m_toppings;
Flavor m_flavor;
class SomeClassA;
enum class Flavor;
enum Toppings : int;
class SomeClassB
{
public:
SomeClassB(void);
~SomeClassB(void);
int GetValueFromSomeClassA(
const SomeClassA* value
);
bool CompareTwoSomeClassAs(
const SomeClassA first,
const SomeClassA second
);
void ChooseFlavor(
Flavor flavor
);
void AddTopping(
Toppings topping
);
void RemoveTopping(
Toppings topping
);
private:
Toppings m_toppings;
Flavor m_flavor;
Static Duration
Global variables, including those inside namespaces, and variables marked
with the duration keyword static have static storage duration.
Global variables are initialized during program initialization (i.e. the period
before the program actually starts execution of your main or wmain function).
They are initialized in the order in which they are defined in the source code. It’s
generally not a good idea to rely on initialization order since refactoring and other
seemingly innocent changes could easily introduce a potential bug into your
program.
Local statics are zero-initialized the first time program execution reaches the
block containing the local static. Typically, they will be initialized to their specified
values or initialized by calling the specified constructor at that point. The value
assignment or construction phase is not required until the program reaches and
executes the statement, except in very rare circumstances. Once a local static is
initialized, the initialization specified with its declaration will never run again. This,
of course, is just what we would expect of a local static. If it kept initializing itself
every time the program reached its definition line, then it would be the same as a
non-static local.
You can assign other values to global and local statics, unless you also make
them const, of course.
Automatic Duration
Automatic Duration
Within a block, an object has automatic duration if it is defined without the new
operator to instantiate it, and without a storage duration keyword, although it can
optionally have the register keyword. This means the object is created at the point
when it is defined and is destroyed when the program exits the block its variable
was declared in, or when a new value is assigned to its variable.
Dynamic Duration
Dynamic duration is the result of using either the new operator or the new[]
operator. The new operator is used to allocate single objects, while the new[]
operator is used to allocate dynamic arrays. You must keep track of the size of a
dynamically allocated array. While the C++ implementation will properly free a
dynamically allocated array, provided you use the delete[] operator, there is no
easy or portable way to determine the size of that allocation. It will likely be
impossible. Single objects are freed with the delete operator.
When you allocate memory using new or new[], the return value is a pointer. A
pointer is a variable that holds a memory address. In C#, if you set all your
references to an object to null or some other value, then the memory is no longer
reachable in your program, so the GC can free that memory for other uses.
In C++, if you set all of your pointers to an object to nullptr or some other
value, and you cannot figure out the original address using pointer arithmetic, then
you have lost your ability to release that memory using the delete or delete[]
operators. You have thereby created a memory leak. If a program leaks enough
memory, eventually it will crash because the system will run out of memory
addresses for it. Even before that, though, the computer will slow horribly, as it is
forced to increase paging to accommodate the ever-increasing memory footprint
of your program (assuming it has virtual memory, which is absent from most smart
phones).
Thread Duration
Thread duration is the least commonly used storage duration. It has only
recently been standardized. As of this writing, few, if any, C++ compiler vendors
have implemented support for the new thread_local keyword from the C++11
standard.
This is certain to change, but for now you can use vendor-specific extensions
such as the Microsoft-specific extension _declspec(thread) or the GCC-specific
extension __thread if you need functionality of this sort.
Thread duration is similar to static duration except instead of lasting the life of
the program, these variables are local to each thread; the thread’s copy exists for
the duration of the thread. Each thread’s instance of a thread duration object is
initialized when it is first used in the thread, and it is destroyed when the thread
exits. A thread duration object does not inherit its value from the thread that
started the thread it exists in.
Choosing the Right Storage Duration
If the object should exist for the whole length of the program’s execution, use
static storage duration.
If the object should exist for the whole length of a particular thread, use
thread storage duration.
If the object will only exist for part of the program or thread’s duration, use
dynamic storage duration.
You can deviate from those recommendations if doing so makes sense, but in
most cases, this guidance will steer you correctly.
Storage Duration Sample
class SomeClass
{
public:
explicit SomeClass(int value = 0);
SomeClass(
int value,
const wchar_t* stringId
);
~SomeClass(void);
private:
int m_value;
std::wstring m_stringId;
};
Sample: StorageDurationSampleSomeClass.cpp
#include “SomeClass.h”
#include string
#include ostream
#include iostream
#include ios
#include iomanip
#include thread
#include memory
SomeClass::SomeClass(int value) :
m_value(value),
m_stringId(L”(No string id provided.)”)
{
SomeClass* localThis = this;
auto addr = reinterpret_castunsigned int(localThis);
wcout L”Creating SomeClass instance.” endl
L”StringId: ” m_stringId.c_str() L”.” endl
L”Address is: ‘0x” setw(8) setfill(L‘0’)
hex addr dec L”’.” endl
L”Value is ’” m_value L”’.” endl
L”Thread id: ’”
this_thread::get_id() L”’.” endl endl;
}
SomeClass::SomeClass(
int value,
const wchar_t* stringId
) : m_value(value),
m_stringId(stringId)
{
SomeClass* localThis = this;
auto addr = reinterpret_castint(localThis);
wcout L”Creating SomeClass instance.” endl
L”StringId: ” m_stringId.c_str() L”.” endl
L”Address is: ‘0x” setw(8) setfill(L‘0’)
hex addr dec L”’.” endl
L”Value is ’” m_value L”’.” endl
L”Thread id: ’”
this_thread::get_id() L”’.” endl endl;
}
SomeClass::~SomeClass(void)
{
// This is just here to clarify that we aren’t deleting a
// new object when we replace an old object with it, despite
// the order in which the Creating and Destroying output is
// shown.
m_value = 0;
SomeClass* localThis = this;
auto addr = reinterpret_castunsigned int(localThis);
wcout L”Destroying SomeClass instance.” endl
L”StringId: ” m_stringId.c_str() L”.” endl
L”Address is: ‘0x” setw(8) setfill(L‘0’)
hex addr dec L”’.” endl
L”Thread id: ’”
this_thread::get_id() L”’.” endl endl;
}
// Note that when creating a static member variable, the definition also
// needs to have the type specified. Here, we start off with
// ‘unique_ptrSomeClass’ before proceeding to the
// ‘SomeClass::s_someClass = …;’ value assignment.
unique_ptrSomeClass SomeClass::s_someClass =
unique_ptrSomeClass(new SomeClass(10, L”s_someClass”));
Sample: StorageDurationSampleStorageDurationSample.cpp
#include iostream
#include ostream
#include sstream
#include thread
#include memory
#include cstddef
#include “SomeClass.h”
#include “../pchar.h”
struct SomeStruct
{
int Value;
};
namespace Value
{
// Visual C++ does not support thread_local as of VS 2012 RC. We can
// partially mimic thread_local with _declspec(thread), but we cannot
// have things as classes with functions (including constructors
// and destructors) with _declspec(thread).
_declspec(thread) SomeStruct ThreadLocalSomeStruct = {};
// Demonstrate _declspec(thread).
wcout L”Old value is ” Value::ThreadLocalSomeStruct.Value
L”. Thread id: ’” this_thread::get_id() L”’.” endl;
Value::ThreadLocalSomeStruct.Value = value;
wcout L”New value is ” Value::ThreadLocalSomeStruct.Value
L”. Thread id: ’” this_thread::get_id() L”’.” endl;
}
LocalStatic(1000);
// Note: The local static in LocalStatic will not be reinitialized
// with 5000. See the function definition for more info.
LocalStatic(5000);
// To demonstrate _declspec(thread) we change the value of this
// thread’s Value::ThreadLocalSomeStruct to 20 from its default 0.
ChangeAndPrintValue(20);
// Wait for the thread we just created to finish executing. Note that
// calling join from a UI thread is a bad idea since it blocks
// the current thread from running until the thread we are calling
// join on completes. For WinRT programming, you want to make use
// of the PPLTasks API instead.
thr.join();
wcout endl;
//// If you wanted to zero out your dynamic array, you could do this:
//uninitialized_fill_n(p_arrInt, arrIntSize, 0);
//// If you un-commented this, then you would have a double delete,
//// which would crash your program.
//delete p_dsc;
//// If you did this, you would have a program error, which may or may
//// not crash your program. Since dsc is not an array, it should not
//// use the array delete (i.e. delete[]), but should use the non-array
//// delete shown previously.
//delete[] p_dsc;
// If you forgot this, you would have a memory leak. If you used
// ‘delete’ instead of ‘delete[]’ unknown bad things might happen. Some
// implementations will overlook it while others would crash or do who
// knows what else.
delete[] p_arrInt;
p_arrInt = nullptr;
Ending program.
Destroying SomeClass instance.
StringId: _pmain sc1.
Address is: ‘0x007bfe98’.
Thread id: ‘3660’.
Destroying SomeClass instance.
StringId: LocalStatic sc.
Address is: ‘0x013f8578’.
Thread id: ‘3660’.
Default Constructor
We’ll start with default constructors. A default constructor can be called with no
arguments. A class can have no more than one default constructor. If you define a
class and include no constructors, you produce an implicit default constructor,
which the compiler creates for you. However, if you have a class member variable
that is a class, structure, or union that has no default constructor, then you must
define at least one constructor because the compiler cannot create an implicit
default constructor.
If you define a constructor with no parameters, it is an explicit default
constructor. It is also possible to define a constructor that takes parameters and
still make it a default constructor using default arguments, which we will discuss in
a moment.
If you define a constructor that has at least one required parameter, then the
compiler will not generate a default constructor. You must define an explicit default
constructor if you want one.
Default Arguments in Function Declarations
Parameterized Constructors
A parameterized constructor has one or more parameters. A parameterized
constructor where all of the parameters have default arguments is also the default
constructor for a class. There is nothing special about parameterized constructors
in C++.
Conversion Constructors
Conversion Constructors
A conversion constructor has at least one parameter. If there is more than one,
then those additional parameters must have default arguments.
If you do not want a constructor to be a conversion constructor, you can mark
it with the function specifier: explicit. Let’s look at an example:
#include string
class SomeClass
{
public:
SomeClass(const wchar_t* value) :
m_strValue(value),
m_intValue()
{
}
~SomeClass(void) { }
void DoSomething(void)
{
// Normal constructor use.
SomeClass sc1 = SomeClass(L”Hello World”);
// …
}
As you can see, the conversion constructor lets us construct s2 by directly
setting it equal to a string value. The compiler sees that statement, checks to see
if SomeClass has a conversion constructor that will receive that sort of value, and
proceeds to call the appropriate SomeClass constructor. If we tried that with the
commented-out sc4 line, the complier would fail because we used explicit to tell
the compiler that the constructor, which just takes int, should not be treated as a
conversion constructor, but instead should be like any other parameterized
constructor.
Conversion constructors can be useful, but they can also lead to bugs. For
example, you could accidentally create a new object and assign it to an existing
variable when you merely mistyped a variable name and really meant an
assignment. The compiler won’t complain if there is a valid conversion
constructor, but will complain if there isn’t. So keep that in mind and remember to
mark single parameter constructors as explicit, except when you have a good
reason for providing a conversion constructor.
Initialization of Data and Base Classes
Note: This sample uses some very bad code practices in order to illustrate
how C++ performs initialization. Specifically, the ordering of initializers in
constructor definitions is misleading in some places. Always make sure your
constructors order their initialization of base classes and parameters in the order
that the initialization will actually occur during program execution. That will help
you avoid bugs and make your code easier to debug and easier to follow.
Sample: InitializationSampleInitializationSample.cpp
#include iostream
#include ostream
#include “../pchar.h”
class A
{
public:
A(void) :
m_value(InitializingIntMsg(5, L”DEFAULT m_value”))
{
wcout L”DEFAULT Constructing A. m_value is ’”
m_value L”’.” endl;
}
explicit A(int) :
m_value(InitializingIntMsg(20, L”m_value”))
{
wcout L”Constructing A. m_value is ’”
m_value L”’.” endl;
}
virtual ~A(void)
{
wcout L”Destroying A.” endl;
}
private:
int m_value;
};
private:
int m_a;
int m_b;
};
class C
{
public:
explicit C(int) :
m_c(InitializingIntMsg(0, L”m_c”))
{
wcout L”Constructing C. m_c is ’”
m_c L”’.” endl;
}
virtual ~C(void)
{
wcout L”Destroying C.” endl;
}
private:
int m_c;
};
class D
{
public:
explicit D(int) :
m_d(InitializingIntMsg(3, L”m_d”))
{
wcout L”Constructing D. m_d is ’”
m_d L”’.” endl;
}
virtual ~D(void)
{
wcout L”Destroying D.” endl;
}
private:
int m_d;
};
private:
int m_someInt;
};
{
Z someZ(CallingMsg(L”Z”));
wcout L”Z::GetSomeInt returns ’”
someZ.GetSomeInt() L”’.” endl;
}
return 0;
}
The first thing we’ve done is define two helper functions that write messages,
so we can easily follow the order in which things are happening. You will notice
that each of the classes has a constructor that takes int, though only Y and Z put it
to use. A, B, C, and D do not even specify a name for the int in their int parameter
constructors; they just specify that there is an int parameter. This is perfectly legal
C++ code, and we’ve been using it all along with our commented-out parameter
names in _pmain.
Class A has two constructors: a default constructor and a parameterized
constructor that takes an int. The remaining classes have only parameterized
constructors, each of which takes an int.
Class B inherits from A virtually.
Class C inherits from nothing.
Class D inherits from nothing.
Class Y inherits from B virtually, from D directly, and from C virtually, in that
order.
Class Z also inherits from D directly, from B virtually, and from C directly, in
that order.
Let’s look at the output this program gives us and then discuss what is
happening and why.
Calling Y constructor.
Initializing DEFAULT m_value.
DEFAULT Constructing A. m_value is ‘5’.
Calling B constructor.
Initializing m_a.
Initializing m_b.
Constructing B. m_a is ‘5’ and m_b is ‘2’.
Calling C constructor.
Initializing m_c.
Constructing C. m_c is ‘0’.
Calling D constructor.
Initializing m_d.
Constructing D. m_d is ‘3’.
Initializing m_someInt.
Constructing Y. m_someInt is ‘0’.
Y::GetSomeInt returns ‘0’.
Destroying Y.
Destroying D.
Destroying C.
Destroying B.
Destroying A.
Since we know B inherits virtually from A, and that B is the only source of
inheritance from A, we can conclude that B is given priority over the others and
that A is given priority over B.
Well, we inherit from B before we inherit from the other classes. So that could
be it, but why doesn’t D become initialized right after B? It’s because D is directly
inherited while C is virtually inherited. The virtuals come first.
Here are the rules:
1. Virtual base classes are constructed in a left-to-right order as they are written
in the list of base classes.
2. If you do not call a specific constructor for a base class you have virtually
inherited from, the compiler will automatically call its default constructor at the
appropriate time.
3. When determining the order of base classes to construct, base classes are
initialized before their derived classes.
4. When virtual base classes have all been constructed, then direct base
classes are constructed in their left-to-right declaration order—the same as
virtual base classes.
5. When all of its base classes have been constructed, a member variable of a
class is:
Default-initialized if there is no initializer for it.
Value-initialized if the initializer is an empty set of parentheses.
Initialized to the result of the expression within the initializer’s
parentheses.
6. Member variables are initialized in the order they are declared in the class
definition.
7. When all the initializers in a constructor have run, any code inside the
constructor’s body will be executed.
Default initialization gives no initialization for fundamental types and for
pointers. It calls the default constructor for class types. Integers, pointers, and the
like will just have arbitrary values.
Value initialization initializes fundamental types to zero or the equivalent (e.g.,
false for bool and null for pointers). It calls the default constructor for class types.
When you put all of these rules together, you initially find the order B, C, D
because B and C have virtual inheritance and thus come before D. Then we add
A before B because B derives from A. So we end up with A, B, C, D.
Because of the rule that base classes are initialized before derived classes,
and because A comes in through virtual inheritance, A is initialized with its default
constructor before we even get to its initializer in the B constructor that we call.
Once we do get to the B constructor, because A is already initialized, its initializer
in the B constructor is simply ignored.
Class B’s member variables are initialized in the order m_a, m_b because that
is the order they are declared in the class, even though in the constructor we list
their initializations in the opposite order.
Delegating Constructor
Delegating Constructor
Note: Visual C++ does not support delegating constructors in Visual Studio
2012 RC.
A delegating constructor calls another constructor of the same class (the target
constructor). The delegating constructor can have only one initializer statement,
which is the call to the target constructor. Its body can have statements; these will
run after the target constructor has completely finished. Here’s an example:
#include iostream
#include ostream
class SomeClass
{
public:
SomeClass(void) : SomeClass(10)
{
// Statements here will execute after the
// statements in the SomeClass(int) constructor
// body have finished executing.
wcout “Running SomeClass::SomeClass(void).” endl;
}
SomeClass(int value)
: m_value(value)
{
// Statements here will execute after the m_value
// initializer above.
wcout “Running SomeClass::SomeClass(int).” endl;
}
return 0;
}
If you compile and run that code using a compiler such as GCC, you will see
the following output:
Running SomeClass::SomeClass(int).
Running SomeClass::SomeClass(void).
SomeClass::GetValue() = 10
Copy Constructor
Copy Constructor
A copy constructor has only one mandatory parameter: a reference to a
variable having the same type as the constructor’s class. A copy constructor can
have other parameters as long as they are all provided with default arguments. Its
purpose is to allow you to construct a copy of an object.
The compiler will provide you with a default copy constructor if it can, and it
can as long as all the member variables that are classes, structures, or unions
have a copy constructor. The copy constructor it provides is a shallow copy
constructor. If you have a pointer to an array of data, for instance, the copy gets a
copy of the pointer, not a new array containing a copy of the same data.
If you then had a delete statement in the destructor for that class, you would
have one copy in an invalid state when the other was destroyed, and you’d have a
runtime error when you tried to delete the memory for the second time when the
remaining copy was destroyed, assuming your program had not already crashed.
This is one of many reasons you should always use smart pointers. We will cover
them in the chapter on RAII.
If you do not wish to use the compiler-provided copy constructor, or if the
compiler cannot provide one, but you want to have one anyway, you can write a
copy constructor. For example, perhaps you want a deeper copy of the data, or
perhaps your class has a std::unique_ptr and you decide what an acceptable
“copy” of it would be for the purposes of your class. We will see an example of this
in ConstructorsSample.
A copy constructor should typically have the following declaration:
SomeClass(const SomeClass);. To avoid weird errors, the constructor should
always take a const lvalue reference to the class you copy from. There’s no
reason you should change the class you are copying from in a copy constructor.
Making it const does no harm and provides some guarantees about your
operation. A copy constructor should not be defined as explicit.
Copy Assignment Operator
Move Constructor
In C++, if all you had was a copy constructor, and you wanted to pass a class
instance into a std::vector (similar to a .NET ListT) or return it from a function, you
would need to make a copy of it. Even if you had no intention of using it again,
you would still incur the time it takes to make a copy. If you’re adding many
elements to a std::vector, or if you wrote a factory function that you use a lot, it
would be a big performance hit.
This is why C++ has a move constructor. The move constructor is new in
C++11. There are some circumstances in which the compiler will provide you one,
but generally you should write your own for the classes you will need it for.
It’s easy to do. Note that if you write a move constructor, then you will also
need to write a copy constructor. Visual C++ does not enforce this rule as of
Visual Studio 2012 RC, but it is part of the C++ language standard. If you need to
compile your program with another compiler, you should make sure that you write
copy constructors and copy assignment operators when you write a move
constructor.
A move constructor should typically have the following declaration:
SomeClass(SomeClass);. It cannot be const (because we will be modifying it) or
explicit.
std::move
std::move
The std::move function helps you write move constructors and move
assignment operators. It’s in the utility header file. It takes a single argument and
returns it in a condition suitable for moving. The object passed in will be returned
as an rvalue reference, unless move semantics were disabled for it, in which case
you will get a compiler error.
Move Assignment Operator
Operator Overloading
Operator overloading is a powerful, advanced feature of C++. You can
overload operators on a per-class basis or globally with a stand-alone function.
Almost every operator in C++ can be overloaded. We will see examples of
overloading the copy assignment, move assignment, , |, and |= operators shortly.
For a list of other operators you can overload, and how such overloads work, I
recommend visiting the MSDN documentation on the subject. It’s a useful feature,
but one you may not commonly use. Looking it up when you need it is often faster
than trying to memorize it right away.
namespace Desserts
{
enum class Flavor
{
None,
Vanilla,
Chocolate,
Strawberry
};
namespace Desserts
{
class Toppings
{
public:
Toppings(void) :
m_toppings(None),
m_toppingsString()
{
}
Toppings(ToppingsList toppings) :
m_toppings(toppings),
m_toppingsString()
{
}
~Toppings(void)
{
}
namespace Desserts
{
class IceCreamSundae
{
public:
IceCreamSundae(void);
IceCreamSundae(Flavor flavor);
IceCreamSundae(IceCreamSundae other);
IceCreamSundae operator=(IceCreamSundae other);
~IceCreamSundae(void);
private:
Flavor m_flavor;
Toppings m_toppings;
std::wstring m_description;
};
}
Sample: ConstructorsSampleIceCreamSundae.cpp
#include “IceCreamSundae.h”
#include string
#include sstream
#include iostream
#include ostream
#include memory
IceCreamSundae::IceCreamSundae(void) :
m_flavor(Flavor::None),
m_toppings(Toppings::None),
m_description()
{
wcout L”Default constructing IceCreamSundae(void).”
endl;
}
IceCreamSundae::IceCreamSundae(Flavor flavor) :
m_flavor(flavor),
m_toppings(Toppings::None),
m_description()
{
wcout L”Conversion constructing IceCreamSundae(Flavor).”
endl;
}
IceCreamSundae::IceCreamSundae(Toppings::ToppingsList toppings) :
m_flavor(Flavor::None),
m_toppings(toppings),
m_description()
{
wcout L”Parameter constructing IceCreamSundae(
Toppings::ToppingsList).” endl;
}
m_flavor = other.m_flavor;
m_toppings = other.m_toppings;
return *this;
}
IceCreamSundae::IceCreamSundae(IceCreamSundae other) :
m_flavor(),
m_toppings(),
m_description()
{
wcout L”Move constructing IceCreamSundae.” endl;
*this = std::move(other);
}
if (this != other)
{
m_flavor = std::move(other.m_flavor);
m_toppings = std::move(other.m_toppings);
m_description = std::move(other.m_description);
other.m_flavor = Flavor::None;
other.m_toppings = Toppings::None;
other.m_description = std::wstring();
}
return *this;
}
IceCreamSundae::~IceCreamSundae(void)
{
wcout L”Destroying IceCreamSundae.” endl;
}
IceCreamSundae s1 = Flavor::Vanilla;
wcout endl
L”Copy constructing s2 from s1.” endl;
IceCreamSundae s2(s1);
wcout endl
L”Copy assignment to s1 from s2.” endl;
s1 = s2;
wcout endl
L”Move constructing s3 from s1.” endl;
IceCreamSundae s3(std::move(s1));
wcout endl
L”Move assigning to s1 from s2.” endl;
s1 = std::move(s2);
return 0;
}
Chapter 6 Resource Acquisition is Initialization
1. When an automatic storage duration object goes out of scope, its destructor
runs.
2. When an exception occurs, all automatic duration objects that have been fully
constructed since the last try-block began are destroyed in the reverse order
they were created before any catch handler is invoked.
3. If you nest try-blocks, and none of the catch handlers of an inner try-block
handles that type of exception, then the exception propagates to the outer try-
block. All automatic duration objects that have been fully constructed within
that outer try-block are then destroyed in reverse creation order before any
catch handler is invoked, and so on, until something catches the exception or
your program crashes.
RAII helps ensure that you release resources, without exceptions occurring, by
simply using automatic storage duration objects that contain the resources. It is
similar to the combination of the System.IDisposable interface along with the
using statement in C#. Once execution leaves the current block, whether through
successful execution or an exception, the resources are freed.
When it comes to exceptions, a key part to remember is that only fully
constructed objects are destroyed. If you receive an exception in the midst of a
constructor, and the last try block began outside that constructor, since the object
isn’t fully constructed, its destructor will not run.
This does not mean its member variables, which are objects, will not be
destroyed. Any member variable objects that were fully constructed within the
constructor before the exception occurred are fully constructed automatic duration
objects. Thus, those member objects will be destroyed the same as any other fully
constructed objects.
This is why you should always put dynamic allocations inside either
std::unique_ptr or std::shared_ptr. Instances of those types become fully
constructed objects when the allocation succeeds. Even if the constructor for the
object you are creating fails further in, the std::unique_ptr resources will be freed
by its destructor and the std::shared_ptr resources will have their reference count
decremented and will be freed if the count becomes zero.
RAII isn’t about shared_ptr and unique_ptr only, of course. It also applies to
other resource types, such as a file object, where the acquisition is the opening of
the file and the destructor ensures that the file is properly closed. This is a
particularly good example since you only need to create that code right just once
—when you write the class—rather than again and again, which is what you need
to do if you write the close logic every place you have to open a file.
How Do I Use RAII?
std::unique_ptr
The unique pointer, std::unique_ptr, is designed to hold a pointer to a
dynamically allocated object. You should use this type only when you want one
pointer to the object to exist. It is a template class that takes a mandatory and an
optional template argument. The mandatory argument is the type of the pointer it
will hold. For instance auto result = std::unique_ptrint(new int()); will create a
unique pointer that contains an int*. The optional argument is the type of deleter.
We see how to write a deleter in a coming sample. Typically, you can avoid
specifying a deleter since the default_deleter, which is provided for you if no
deleter is specified, covers almost every case you can imagine.
A class that has std::unique_ptr as a member variable cannot have a default
copy constructor. Copy semantics are disabled for std::unique_ptr. If you want a
copy constructor in a class that has a unique pointer, you must write it. You should
also write an overload for the copy operator. Normally, you want std::shared_ptr in
that case.
However, you might have something like an array of data. You may also want
any copy of the class to create a copy of the data as it exists at that time. In that
case, a unique pointer with a custom copy constructor could be the right choice.
std::unique_ptr is defined in the memory header file.
std::unique_ptr has four member functions of interest.
The get member function returns the stored pointer. If you need to call a
function that you need to pass the contained pointer to, use get to retrieve a copy
of the pointer.
The release member function also returns the stored pointer, but release
invalidates the unique_ptr in the process by replacing the stored pointer with a null
pointer. If you have a function where you want to create a dynamic object and
then return it, while still maintaining exception safety, use std:unique_ptr to store
the dynamically created object, and then return the result of calling release. This
gives you exception safety while allowing you to return the dynamic object without
destroying it with the std::unique_ptr’s destructor when the control exits from the
function upon returning the released pointer value at the end.
The swap member function allows two unique pointers to exchange their
stored pointers, so if A is holding a pointer to X, and B is holding a pointer to Y, the
result of calling A::swap(B); is that A will now hold a pointer to Y, and B will hold a
pointer to X. The deleters for each will also be swapped, so if you have a custom
deleter for either or both of the unique pointers, be assured that each will retain its
associated deleter.
The reset member function causes the object pointed to by the stored pointer,
if any, to be destroyed in most cases. If the current stored pointer is null, then
nothing is destroyed. If you pass in a pointer to the object that the current stored
pointer points to, then nothing is destroyed. You can choose to pass in a new
pointer, nullptr, or to call the function with no parameters. If you pass in a new
pointer, then that new object is stored. If you pass in nullptr, then the unique
pointer will store null. Calling the function with no parameters is the same as
calling it with nullptr.
std::shared_ptr
std::shared_ptr
The shared pointer, std::shared_ptr, is designed to hold a pointer to a
dynamically allocated object and to keep a reference count for it. It is not magic; if
you create two shared pointers and pass them each a pointer to the same object,
you will end up with two shared pointers—each with a reference count of 1, not 2.
The first one that is destroyed will release the underlying resource, giving
catastrophic results when you try to use the other one or when the other one is
destroyed and tries to release the already released underlying resource.
To use the shared pointer properly, create one instance with an object pointer
and then create all other shared pointers for that object from an existing, valid
shared pointer for that object. This ensures a common reference count, so the
resource will have a proper lifetime. Let’s look at a quick sample to see the right
and wrong ways to create shared_ptr objects.
Sample: SharedPtrSampleSharedPtrSample.cpp
#include memory
#include iostream
#include ostream
#include “../pchar.h”
struct TwoInts
{
TwoInts(void) : A(), B() { }
TwoInts(int a, int b) : A(a), B(b) { }
int A;
int B;
};
return 0;
}
std::shared_ptr is defined in the memory header file.
std::shared_ptr has five member functions of interest.
The get member function works the same as the std::unique_ptr::get member
function.
The use_count member function returns a long, which tells you what the
current reference count for the target object is. This does not include weak
references.
The unique member function returns a bool, informing you whether this
particular shared pointer is the sole owner of the target object.
The swap member function works the same as the std::unique_ptr::swap
member function, with the addition that the reference counts for the resources
stay the same.
The reset member function decrements the reference count for the underlying
resource and destroys it if the resource count becomes zero. If a pointer to an
object is passed in, the shared pointer will store it and begin a new reference
count for that pointer. If nullptr is passed in, or if no parameter is passed, then the
shared pointer will store null.
std::make_shared
std::make_shared
The std::make_shared template function is a convenient way to construct an
initial std::shared_ptr. As we saw previously in SharedPtrSample, you pass the
type as the template argument and then simply pass in the arguments, if any, for
the desired constructor. std::make_shared will construct a heap instance of the
template argument object type and make it into a std::shared_ptr. You can then
pass that std::shared_ptr as an argument to the std::shared_ptr constructor to
create more references to that shared object.
ComPtr in WRL for Metro-Style Apps
Exceptions in C++
Unlike .NET, where all exceptions derive from System.Exception and have
guaranteed methods and properties, C++ exceptions are not required to derive
from anything; nor are they even required to be class types. In C++, throw L”Hello
World!”; is perfectly acceptable to the compiler as is throw 5;. Basically,
exceptions can be anything.
That said, many C++ programmers will be unhappy to see an exception that
does not derive from std::exception (found in the exception header). Deriving all
exceptions from std::exception provides a way to catch exceptions of unknown
type and retrieve information from them via the what member function before re-
throwing them. std::exception::what takes no parameters and returns a const
char* string, which you can view or log so you know what caused the exception.
There is no stack trace—not counting the stack-trace capabilities your
debugger provides—with C++ exceptions. Because automatic duration objects
within the scope of the try-block that catches the exception are automatically
destroyed before the appropriate catch handler, if any, is activated, you do not
have the luxury of examining the data that may have caused the exception. All
you have to work with initially is the message from the what member function.
If it is easy to recreate the conditions that led to the exception, you can set a
breakpoint and rerun the program, allowing you to step through execution of the
trouble area and possibly spot the issue. Because that is not always possible, it is
important to be as precise as you can with the error message.
When deriving from std::exception, you should make sure to override the what
member function to provide a useful error message that will help you and other
developers diagnose what went wrong.
Some programmers use a variant of a rule stating that you should always
throw std::exception-derived exceptions. Remembering that the entry point (main
or wmain) returns an integer, these programmers will throw std::exception-derived
exceptions when their code can recover, but will simply throw a well-defined
integer value if the failure is unrecoverable. The entry-point code will be wrapped
in a try-block that has a catch for an int. The catch handler will return the caught
int value. On most systems, a return value of 0 from a program means success.
Any other value means failure.
If there is a catastrophic failure, then throwing a well-defined integer value
other than 0 can help provide some meaning. Unless you are working on a project
where this is the preferred style, you should stick to std::exception-derived
exceptions, since they let programs handle exceptions using a simple logging
system to record messages from exceptions not handled, and they perform any
cleanup that is safe. Throwing something that doesn’t derive from std::exception
would interfere with these error-logging mechanisms.
One last thing to note is that C#’s finally construct has no equivalent in C++.
The RAII idiom, when properly implemented, makes it unnecessary since
everything will have been cleaned up.
C++ Standard Library Exceptions
namespace CppForCsExceptions
{
class InvalidArgumentExceptionBase :
public std::invalid_argument
{
public:
InvalidArgumentExceptionBase(void) :
std::invalid_argument(””) { }
template class T
class InvalidArgumentException :
public InvalidArgumentExceptionBase
{
public:
inline InvalidArgumentException(
const char* className,
const char* functionSignature,
const char* parameterName,
T parameterValue
);
private:
std::string m_whatMessage;
};
templateclass T
InvalidArgumentExceptionT::InvalidArgumentException(
const char* className,
const char* functionSignature,
const char* parameterName,
T parameterValue) : InvalidArgumentExceptionBase(),
m_whatMessage()
{
std::stringstream msg;
msg className “::” functionSignature
” - parameter ’” parameterName ”’ had invalid value ’”
parameterValue ”’.”;
m_whatMessage = std::string(msg.str());
}
templateclass T
InvalidArgumentExceptionT::~InvalidArgumentException(void) throw()
{}
templateclass T
const char* InvalidArgumentExceptionT::what(void) const throw()
{
return m_whatMessage.c_str();
}
}
Sample: ExceptionsSampleExceptionsSample.cpp
#include iostream
#include ostream
#include memory
#include exception
#include stdexcept
#include typeinfo
#include algorithm
#include cstdlib
#include “InvalidArgumentException.h”
#include “../pchar.h”
class ThrowClass
{
public:
ThrowClass(void)
: m_shouldThrow(false)
{
wcout L”Constructing ThrowClass.” endl;
}
~ThrowClass(void)
{
wcout L”Destroying ThrowClass.” endl;
}
private:
bool m_shouldThrow;
};
class RegularClass
{
public:
RegularClass(void)
{
wcout L”Constructing RegularClass.” endl;
}
~RegularClass(void)
{
wcout L”Destroying RegularClass.” endl;
}
};
class ContainStuffClass
{
public:
ContainStuffClass(void) :
m_regularClass(new RegularClass()),
m_throwClass(new ThrowClass())
{
wcout L”Constructing ContainStuffClass.” endl;
}
private:
unique_ptrRegularClass m_regularClass;
shared_ptrThrowClass m_throwClass;
};
void TerminateHandler(void)
{
wcout L”Terminating due to unhandled exception.” endl;
ThrowClass tC(false);
wcout L”tC should throw? ” tC.GetShouldThrow() endl;
tC = ThrowClass(true);
wcout L”tC should throw? ” tC.GetShouldThrow() endl;
}
// One downside to using templates for exceptions is that you need a
// catch handler for each specialization, unless you have a base
// class they all inherit from, that is. To avoid catching
// other std::invalid_argument exceptions, we created an abstract
// class called InvalidArgumentExceptionBase, which serves solely to
// act as the base class for all specializations of
// InvalidArgumentExceptionT. Now we can catch them all, if desired,
// without needing a catch handler for each. If you wanted to, however,
// you could still have a handler for a particular specialization.
catch (InvalidArgumentExceptionBase e)
{
wcout L”Caught ’” typeid(e).name() L”’.” endl
L”Message: ” e.what() endl;
}
// Catch anything derived from std::exception that doesn’t already
// have a specialized handler. Since you don’t know what this is, you
// should catch it, log it, and re-throw it.
catch (std::exception e)
{
wcout L”Caught ’” typeid(e).name() L”’.” endl
L”Message: ” e.what() endl;
// Just a plain throw statement like this is a re-throw.
throw;
}
// This next catch catches everything, regardless of type. Like
// catching System.Exception, you should only catch this to
// re-throw it.
catch (…)
{
wcout L”Caught unknown exception type.” endl;
throw;
}
return 0;
}
Though I mention it in the comments, I just wanted to point out again that you
will not see the custom terminate function run unless you run this sample from a
command prompt. If you run it in Visual Studio, the debugger will intercept the
program and orchestrate its own termination after giving you a chance to examine
the state to see if you can determine what went wrong. Also, note that this
program will always crash. This is by design since it allows you to see the
terminate handler in action.
Chapter 7 Pointers, References, and Const-Correctness
Const Pointer
A const pointer takes the form SomeClass* const someClass2 = someClass1;.
In other words, the * comes before const. The result is that the pointer itself
cannot point to anything else, but the data the pointer points at remains mutable.
This is not likely to be very useful in most situations.
Pointer to Const
Pointer to Const
A pointer to const takes the form const SomeClass* someClass2 =
someClass1;. In this case the * comes after const. The result is that the pointer
can point to other things, but you cannot modify the data it points to. This is a
common way to declare parameters that you simply want to inspect without
modifying their data.
Const Pointer to Const
References
References act just like non-pointer variables. Once a reference is initialized, it
cannot refer to another object. You also must initialize a reference where you
declare it. If your functions take references rather than objects, you will not incur
the cost of a copy construction. Since the reference refers to the object, changes
to it are changes to the object itself.
Just like pointers, you can also have a const reference. Unless you need to
modify the object, you should use const references since they provide compiler
checks to ensure that you aren’t mutating the object when you think you aren’t.
There are two types of references: L-value references and R-value references.
An L-value reference is marked by an appended to the type name (e.g.,
SomeClass), whereas an R-value reference is marked by an appended to the
type name (e.g., SomeClass). For the most part, they act the same; the main
difference is that the R-value reference is extremely important to move semantics.
Pointer and Reference Sample
// These two statements are fine since we can modify the data that a
// const pointer points to.
// Set all elements to 5.
uninitialized_fill_n(cp_intArr, intArrCount, 5);
// Sets the first element to zero.
*cp_intArr = 0;
//// This statement is illegal since we cannot modify the data that a
//// pointer to const points to.
//*pc_intArr = 10;
*p_intArr = 6;
SetValueToZero(*p_intArr);
*p_intArr = 9;
SetValueToZero(r_first);
assert(*p_intArr == 0);
value = cr_first;
*p_firstElement = 10;
SetValueToZero(*p_firstElement);
assert(*p_firstElement == 0);
*p_intArr = 3;
SetValueToZero((*p_intArr));
assert(*p_firstElement == 0);
*p_intArr = 20;
assert(*p_intArr == 0);
*p_intArr = 50;
assert(*p_intArr == 0);
Volatile
I mention volatile only to caution against using it. Like const, a variable can be
declared volatile. You can even have a const volatile; the two are not mutually
exclusive.
Here’s the thing about volatile: It likely does not mean what you think it means.
For example, it is not good for multithreaded programming. The actual use case
for volatile is extremely narrow. Chances are, if you put the volatile qualifier on a
variable, you are doing something horribly wrong.
Eric Lippert, a member of the C# language team at Microsoft, described the
use of volatile as, “A sign that you are doing something downright crazy: You’re
attempting to read and write the same value on two different threads without
putting a lock in place.” He’s right, and his argument carries over perfectly into
C++.
The use of volatile should be greeted with more skepticism than the use of
goto. I say this because I can think of at least one valid general-purpose use of
goto: breaking out of a deeply nested loop construct upon the completion of a
non-exceptional condition. volatile, by contrast, is really only useful if you are
writing a device driver or writing code for some type of ROM chip. On that point,
you really should be thoroughly familiar with the ISO/IEC C++ Programming
Language Standard itself, the hardware specs for the execution environment your
code will be running in, and probably the ISO/IEC C Language Standard too.
Note: You should also be familiar with assembly language for the target
hardware, so you can look at code that is generated and make sure the compiler
is generating correct code (PDF) for your use of volatile.
I have been ignoring the existence of the volatile keyword and shall continue to
do so for the remainder of this book. This is perfectly safe, since:
It’s a language feature that doesn’t come into play unless you actually use it.
Its use can safely be avoided by virtually everyone.
One last note about volatile: The one effect it is very likely to produce is slower
code. Once upon a time, people thought volatile produced the same result as
atomicity. It doesn’t. When properly implemented, atomicity guarantees that
multiple threads and multiple processors cannot read and write an atomically
accessed chunk of memory at the same time. The mechanisms for this are locks,
mutexes, semaphones, fences, special processor instructions, and the like. The
only thing volatile does is force the CPU to fetch a volatile variable from memory
rather than use any value it might have cached in a register or on a stack. It is the
memory fetching that slows everything down.
Chapter 8 Casting in C++
const_cast
The const_cast operator can add and remove const and volatile. Using it to
add either of these attributes is fine. It’s rare that you would, but if you do, you
can.
Its ability to remove const is something you should never use in a C++
program except when you need to call a C-language function that doesn’t abide
by const-correctness but does not modify the object at all. If a function has a const
parameter, and throws out the const-ness of it by using const_cast, the function is
breaking the implied contract that it will not modify the parameter. So it’s up to you
as the author of that function to ensure that you are not going to modify the object;
otherwise, you should not use const for the parameter since you will be modifying
the object.
If you ever need to use const_cast and another cast operator on the same
object, use const_cast last, since removing const-ness from an object could allow
unintended changes to take place if you used a subsequent cast.
static_cast
static_cast
The static_cast operator is useful for casting:
dynamic_cast
The dynamic_cast operator is useful for casting through virtual inheritance.
static_cast can cast from a derived class to a base class, whether the inheritance
is virtual or not. Say, however, you are given an object of type A, but you know it is
actually an object of type B—and that B inherits virtually from A. If you want to
cast this object back to B to use member functions that only B provides, you need
to use dynamic_cast.
A few things about dynamic_cast. First, it only works on pointer-to-pointer or
reference-to-reference conversions. Second, it can’t actually cast an object from
an A to a B if the object is not, in fact, a B (or of a type derived from B). A pointer-
to-pointer dynamic_cast that fails returns null. A reference-to-reference failure
throws a std::bad_cast exception.
reinterpret_cast
reinterpret_cast
The reinterpret_cast operator is a direct conversion with very few good uses.
Most its operations give undefined results. What this means in practice is that you
should read the compiler vendor’s documentation before using it for anything.
One use for it, as we saw in StorageDurationSample, is to cast a pointer to
an integer type large enough to hold it. This gives the memory address of the
pointer, which can be useful for debugging and tracing operations where you can
dump data to log files and create core dumps, but it may not be able to run a
debugger easily. You will see it used legitimately at times for other purposes, but
in general, it should be considered as the cast of last resort (excluding a C-style
cast, which comes after reinterpret_cast).
C-style Cast
C-style Cast
The C-style cast, (e.g., auto someData = (SomeType)dataOfSomeOtherType;)
is not your friend. You are undoubtedly familiar with it from C#, where it is highly
useful. In C#, if you try to make a cast using that syntax, and the cast is invalid,
you will produce an InvalidCastException. This happens because the CLR keeps
track of the types of everything you’ve created and detects bad casts.
C++ does not check to see if your C-style cast is valid, assuming it compiles,
of course. C++ just assumes that it is. If it’s a bad cast, and you are lucky, your
program will crash immediately. If not, you will end up with data in an unknown
state, which will certainly become corrupted in subtle and insidious ways.
Further, unlike the other casts, which you can easily spot by looking for _cast,
C-style casts do not stick out. When you’re scanning lots of code quickly,
parentheses wrapped around text looks as much like a function call as it does a
cast operation. You could use a regular expression search for this in Visual Studio
2012: (.*)[A-Za-z]. Even so, you are still forgoing all the benefits and protections of
the other casts.
The one thing a C-style cast can do that other casts cannot is cast an object to
one of its protected or private inheritance base classes. You really shouldn’t do
this since, if you need public inheritance, you should use public inheritance.
In short, don’t use C-style casts.
Sample
Sample
There is a sample, CastingSample, that demonstrates the many possible
types of casting. It is included with the source code for this book. In the interest of
brevity, I am omitting it here.
Chapter 9 Strings
Chapter 9 Strings
Introduction
Strings are one of those troublesome things in C and C++. In the early days of
the languages, strings were all character arrays, typically 7-bit ASCII (though
perhaps EBCDIC on IBM mainframes that C was ported to). Then came a mess of
OS-specific workarounds, such as code pages, to allow for languages with
characters that were not in the English alphabet. After a period of chaos, came
Unicode. Then Unicode. And then Unicode again. And a few more Unicodes here
and there as well, which is the root of the problem today.
Unicode is, in essence, two things. It’s a defined series of code points in which
there is a one-to-one mapping of a particular code point to a particular value,
some are graphic, others control and manipulate formatting or provide other
required information. Everyone who uses Unicode agrees on all of these,
including the private-use code points, which all agree are reserved for Unicode-
conforming applications. So far, so good.
Then there are the encoding schemes where the divisions come from. There
are 1,114,112 code points in Unicode. How do you represent them? The answer
was the encoding schemes. UTF-16 was the first. It was later followed by UTF-8
and UTF-32. There are also endianness issues with some of these.
Other formats came and went, some of which were never even part of
Unicode.
Windows ultimately adopted UTF-16 as did .NET and Java. Many GNU/Linux
and other UNIX-like systems adopted UTF-8. Some UNIX-like systems use UTF-
32. Some might use UTF-16. The web uses UTF-8 for the most part, due to that
encoding’s intentional design to be mostly backward compatible with ASCII. As
long as you are working on one system, all is well. When you try to become cross-
platform, things can become more confusing.
char* Strings
char* Strings
The char* strings (pointers to arrays of char) originally meant ASCII strings.
Now they sometimes mean ASCII, but more frequently, they mean UTF-8. This is
especially true in the UNIX world.
When programming for Windows, generally, you should assume that a char*
string is an ASCII string or a code-page string. Code pages use the extra bit left
over from 7-bit ASCII to add another 128 characters, thus creating a lot of
localized text still fitting within one byte per character.
wchar_t* Strings
wchar_t* Strings
wchar_t* strings (pointers to arrays of wchar_t, also called wide characters)
use a different, implementation-dependent character set. On Windows, this
means a 16-bit value, which is used for UTF-16. You should always work with
wchar_t as your native character type for Windows unless you have to support
really, really old OS versions (i.e., the old Windows 9X series).
When you write a wide character string constant in code, you prefix the
opening double quotes with an L. For example: const wchar_t* s = L”Hello
World”;. If you only need a single character, you again use the L, but with single
quotes: wchar_t ch = L’A’;.
std::string and std::wstring Strings
std::wstringstream Strings
The std::wstringstream class (there is a std::stringstream as well) is similar to
the .NET StringBuilder class. It is usable in much the same way as any other C++
Standard Library stream. I find this type very useful for constructing a string within
a member function that will then be stored in a std::wstring class member.
For an example of its usage, see the Toppings::GetString member function in
the ConstructorsSampleToppings.h file. Here is its code, just as a refresher:
const wchar_t* GetString(void)
{
if (m_toppings == None)
{
m_toppingsString = L”None”;
return m_toppingsString.c_str();
}
bool addSpace = false;
std::wstringstream wstrstream;
if (m_toppings HotFudge)
{
if (addSpace)
{
wstrstream L” “;
}
wstrstream L”Hot Fudge”;
addSpace = true;
}
if (m_toppings RaspberrySyrup)
{
if (addSpace)
{
wstrstream L” “;
}
wstrstream L”Raspberry Syrup”;
addSpace = true;
}
if (m_toppings CrushedWalnuts)
{
if (addSpace)
{
wstrstream L” “;
}
wstrstream L”Crushed Walnuts”;
addSpace = true;
}
if (m_toppings WhippedCream)
{
if (addSpace)
{
wstrstream L” “;
}
wstrstream L”Whipped Cream”;
addSpace = true;
}
if (m_toppings Cherry)
{
if (addSpace)
{
wstrstream L” “;
}
wstrstream L”Cherry”;
addSpace = true;
}
m_toppingsString = std::wstring(wstrstream.str());
return m_toppingsString.c_str();
}
Chapter 10 C++ Language Usages and Idioms
// int x = ++i;
i = i + 1;
int x = i;
// int y = i++;
int magicTemp = i;
i = i + 1;
int y = magicTemp;
Early compilers, in fact, used to do things like that. Modern compilers now
determine that there are no observable side effects to assigning to y first, so the
assembly code they generate, even without optimization, will typically look like the
assembly-language equivalent of this C++ code:
int i = 0;
// int x = ++i;
i = i + 1;
int x = i;
// int y = i++;
int y = i;
i = i + 1;
In some ways, the ++i syntax (especially within a for loop) is a holdover from
the early days of C++, and even C before it. Knowing that other C++ programmers
use it, employing it yourself lets others know you have at least some familiarity
with C++ usages and style—the secret handshake. The useful part is that you can
write a single line of code, int x = ++i;, and get the result you desire rather than
writing two lines of code: i++; followed by int x = i;.
Tip: While you can save a line of code here and there with tricks such as
capturing the pre-increment operator’s result, it’s generally best to avoid
combining a bunch of operations in a single line. The compiler isn’t going to
generate better code, since it will just decompose that line into its component
parts (the same as if you had written multiple lines). Hence, the complier will
generate machine code that performs each operation in an efficient manner,
obeying the order of operations and other language constraints. All you’ll do is
confuse other people who have to look at your code. You’ll also introduce a
perfect situation for bugs, either because you misused something or because
someone made a change without understanding the code. You’ll also increase the
likelihood that you yourself will not understand the code if you come back to it six
months later.
Concerning Null – Use nullptr
p_x = 0;
Did you mean to set the pointer to null as written (i.e. p_x = 0;) or did you
mean to set the pointed-to value to 0 (i.e. *p_x = 0;)? Even with code of
reasonable complexity, the debugger could take significant time to diagnose such
errors.
The result of this realization was the adoption of the NULL preprocessor
macro: #define NULL 0. This would help reduce errors, if you saw *p_x = NULL;
or p_x = 0; then, assuming you and the other programmers were using the NULL
macro consistently, the error would be easier to spot, fix, and the fix would be
easier to verify.
But because the NULL macro is a preprocessor definition, the compiler would
never see anything other than 0 due to textual substitution; it could not warn you
about possibly erroneous code. If someone redefined the NULL macro to another
value, all sorts of additional problems could result. Redefining NULL is a very bad
thing to do, but sometimes programmers do bad things.
C++11 has added a new keyword, nullptr, which can and should be used in
place of 0, NULL, and anything else when you need to assign a null value to a
pointer or check to see if a pointer is null. There are several good reasons to use
it.
The nullptr keyword is a language keyword; it is not eliminated by the
preprocessor. Since it passes through to the compiler, the compiler can detect
errors and generate usage warnings that it couldn’t detect or generate with the
literal 0 or any macros.
It also cannot be redefined either accidentally or intentionally, unlike a macro
such as NULL. This eliminates all the errors that macros can introduce.
Lastly, it provides future proofing. Having binary zero as the null value was a
practical decision when it was made, but it was arbitrary nonetheless. Another
reasonable choice might have been to have null be the max value of an unsigned
native integer. There are positives and negatives to such a value, but there’s
nothing I know of that would have made it unusable.
With nullptr, it suddenly becomes feasible to change what null is for a
particular operating environment without making changes to any C++ code that
has fully adopted nullptr. The compiler can take a comparison with nullptr, or the
assignment of nullptr to a pointer variable, and generate whatever machine code
the target environment requires from it. Trying to do the same with a binary 0
would be very difficult, if not impossible. If in the future someone decides to design
a computer architecture and operating system that adds a null flag bit for all
memory addresses to designate null, modern C++ could support that because of
nullptr.
Strange-Looking Boolean Equality Checks
Note: As of Visual Studio 2012 RC, the Visual C++ compiler accepts but does
not implement exception specifications. However, if you include a throw()
exception specification, the compiler will likely optimize away any code it would
otherwise generate to support unwinding when an exception is thrown. Your
program may not run properly if an exception is thrown from a function marked
with throw(). Other compilers that do implement throw specifications will expect
them to be marked properly, so you should implement proper exception
specifications if your code needs to be compiled with another compiler.
class A
{
public:
A(void) throw(…);
virtual ~A(void) throw();
class B : public A
{
public:
B(void); // Fine, since not having a throw is the same as throw(…).
virtual ~B(void) throw(); // Fine since it matches ~A.
// The int Add override is fine since you can always throw less in
// an override than the base says it can throw.
virtual int Add(int, int) throw() override;
// The float Add override here is invalid because the A version says
// it will not throw, but this override says it can throw an
// std::exception.
virtual float Add(float, float) throw(std::exception) override;
// The double Add override here is invalid because the A version says
// it can throw an int, but this override says it can throw a double,
// which the A version does not specify.
virtual double Add(double, double) throw(double) override;
};
Because the throw exception specification syntax is deprecated, you should
only use the empty parentheses form of it, throw(), in order to specify that a
particular function does not throw exceptions; otherwise, just leave it off. If you
want to let others know what exceptions your functions can throw, consider using
comments in your header files or in other documentation, making sure to keep
them up-to-date.
noexcept(bool expression) is also an operator. When used as an operator, it
takes an expression that will evaluate to true if it cannot throw an exception, or
false if it can throw an exception. Note that the result is a simple evaluation; it
checks to see if all functions called are noexcept(true), and if there are any throw
statements in the expression. If it finds any throw statements, even ones that you
know are unreachable, (e.g., if (x % 2 0) { throw “This computer is broken”; }) it
can, nonetheless, evaluate to false since the compiler is not required to do a
deep-level analysis.
Pimpl (Pointer to Implementation)
class SandwichImpl;
class Sandwich
{
public:
Sandwich(void);
~Sandwich(void);
private:
vectorwstring m_ingredients;
wstring m_breadType;
wstring m_description;
};
SandwichImpl::SandwichImpl()
{
}
SandwichImpl::~SandwichImpl()
{
}
if (it != m_ingredients.end())
{
m_ingredients.erase(it);
}
}
return m_description.c_str();
}
Sandwich::Sandwich(void)
: m_pImpl(new SandwichImpl())
{
}
Sandwich::~Sandwich(void)
{
}
#include “Sandwich.h”
#include “../pchar.h”
return 0;
}
Chapter 11 Templates
Chapter 11 Templates
Overview
Template functions and classes serve a similar purpose in C++ as generics
serve in C#. They allow you to reuse your code without writing a function or class
for each variant you want. As long as the types you supply to the template have
the functionality associated with them that the template uses, all is well. If not,
then the compiler will generate an error. This is because the compiler is
generating a unique class for each specialization you use. Because the compiler
builds classes and functions from your program’s templates, template functions
and classes must be put into header files and defined entirely inline. That way, the
compiler can parse them for all source code files that use them.
Ultimately, templates can become very complex. The C++ Standard Library
demonstrates the power and complexity of advanced templates. Despite that, you
do not need an advanced knowledge of templates to use them effectively. An
understanding of the fundamentals of C++ templates will help you unlock a
significant amount of functionality and power.
Template Functions
Template Functions
A template function is a stand-alone function that takes at least one template
argument. The fact that it takes an argument makes it incomplete until it is called
with a concrete argument, thereby causing the template to become a fully defined
function. Here is a template function that takes in two arguments.
Sample: TemplatesSamplePeekLastItem.h
#pragma once
templateclass T, class U
U PeekLastItem(T collection)
{
return *collection.rbegin();
}
The creation of any template—function or class—starts with the keyword
template followed by the parameters within arrow brackets, as seen in the
previous sample with class T and class U. The use of the word class before T and
U does not mean those arguments must be classes. Think of class instead as a
general word intended to convey the meaning of a non-specific type. You could
have a template with concrete types or with a mixture of non-specific class types
and concrete types, such as templateclass T, int. The use of T as the name for the
first argument and U for a second is a common practice, not a requirement. You
could use almost anything as a template argument name.
The previous function takes in a reference to an item of type T. It presumes
that T will have a member function called rbegin, which can be called with no
arguments and will return a pointer type that, when de-referenced, will become an
object of type U. This particular function is designed primarily to work with many of
the C++ Standard Library’s collection classes, though any class that meets the
assumptions the function makes about type T can be used with this template
function. That ability to take any type, add the requisite functionality to it, and
thereby make it eligible for use with a template is the main draw of templates.
Template Classes
Template Classes
Template classes are similar to template functions, only they are classes rather
than simple stand-alone functions. Let’s look at an example.
Sample: TemplatesSampleSimpleMath.h
#pragma once
templateclass T
class SimpleMath
{
public:
SimpleMath(void) { }
~SimpleMath(void) { }
T Add(T a, T b)
{
return a + b;
}
T Subtract(T a, T b)
{
return a - b;
}
T Multiply(T a, T b)
{
return a * b;
}
T Divide(T a, T b)
{
return a / b;
}
};
As the name implies, this class is not meant to be more than a demonstration.
There is only one type argument. Looking at the class definition, we can deduce
that the requirements for T in this case are that it has the following operators, all of
which operate on two instances of T and return an instance of T:
+
-
*
/
While these logically belong with numbers, you can define these operators for
any class or data type and then instantiate an instance of this template class,
which is specialized for your custom class (e.g., a Matrix class).
One last note, if you were to define the member functions outside of the class
definition, but still inside the same header file, of course, then you would need to
use the inline keyword in the declarations; the definitions would look like this:
SimpleMathT::SimpleMath(void) { }.
This is the last file of this sample, showing a simple usage of each of the
preceding templates.
Sample: TemplatesSampleTemplatesSample.cpp
#include iostream
#include ostream
#include vector
#include “SimpleMath.h”
#include “PeekLastItem.h”
#include “../pchar.h”
return 0;
}
Chapter 12 Lambda Expressions
lm2(3,4);
Specifying a Lambda’s Return Type
[]()
{
wcout a L” + ” b ” = ” (a + b) endl;
// By capturing by reference, we now do not need
// to mark this as mutable.
// Because it is a reference, though, changes now
// propagate out.
a = 10;
}();
Iterators
Iterators serve the same purpose as IEnumerable and related interfaces in
.NET, such as providing a common way to navigate through collections. Given a
std::vector, for example, you can loop through its collection of items using the
following code:
vectorint vec;
vec.push_back(1);
vec.push_back(4);
vec.push_back(7);
vec.push_back(12);
vec.push_back(8);
Code Snippets
Code snippets are a new feature for C++ in Visual Studio 2012. They did not
exist in earlier versions. If you’ve never used them in any language, then in a C#
project, start typing “for” to begin a for loop; once IntelliSense has chosen the for
snippet, press the Tab key twice and watch as a for loop appears complete with
automatic fields you can edit. Use the Tab key to switch between fields. When
you’re done editing the fields, press Enter. The cursor will be transported within
the loop body with the field edits you made appearing as normal text.
Code snippets are particularly nice for switch statements that switch on an
enum, since they will automatically populate the switch statement with all of the
enum’s members.
Including Libraries
Including Libraries
In C++, it’s usually not enough to just include a header file. Normally you need
to tell the linker to link against a library that implements the code declared in the
header file. To do this, you need to edit the project’s properties, accessible in the
Project menu as ProjectName Properties…
In the properties, under Configuration Properties Linker Input, one of the
fields is Additional Dependencies. This is a semicolon-separated list of the .LIB
files you need to link against. It should end with %(AdditionalDependencies) so
that any additional libraries linked via MS Build will be added.
For a typical DirectX 11 Metro-style game, for example, you might see the
following:
d2d1.lib; d3d11.lib; dxgi.lib; ole32.lib; windowscodecs.lib; dwrite.lib;
xaudio2.lib; xinput.lib; mfcore.lib; mfplat.lib; mfreadwrite.lib; mfuuid.lib; %
(AdditionalDependencies)
If you receive a linker error telling you it cannot find a definition of something
you are using, find the function or class on MSDN; the documentation will tell you
both the header file and the library file you need.
Generating Assembly Code Files
http://www.bookrix.com/-ir0c7a3135af765
ISBN: 978-3-7396-3506-4