278 47 148MB
English Pages 733 [794] Year 2019
MODERN C++, COMPILED AND OPTIMIZED
Written for intermediate to advanced programmers, C++ Crash Course cuts through the weeds to get straight to the core of C++17, the most modern revision of the ISO standard. Part I covers the core C++ language, from types and functions to the object life cycle and expressions. Part II introduces the C++ Standard Library and Boost Libraries, where you’ll learn about special utility classes, data structures, and algorithms, as well as how to manipulate file systems and build high-performance programs that communicate over networks. You’ll learn all the major features of modern C++, including: • Fundamental types, reference types, and userdefined types • Compile-time polymorphism with templates and runtime polymorphism with virtual classes
• The object lifecycle including storage duration, call stacks, memory management, exceptions, and the RAII (resource acquisition is initialization) paradigm • Advanced expressions, statements, and functions • Smart pointers, data structures, dates and times, numerics, and probability/statistics facilities • Containers, iterators, strings, and algorithms • Streams and files, concurrency, networking, and application development With well over 500 code samples and nearly 100 exercises, C++ Crash Course is sure to help you build a strong C++ foundation. ABOUT THE AUTHOR
Josh Lospinoso served for 15 years in the US Army and built the C++ course used by the US Cyber Command to teach its junior developers. He has published over 20 peer-reviewed articles and co-founded a successfully acquired security company. A Rhodes Scholar, Lospinoso holds a PhD in Statistics from the University of Oxford.
w w w.nostarch.com
$59.95 ($78.95 CDN) SHELVE IN: PROGRAMMING LANGUAGES/C++
LOSPINOSO
T H E F I N E ST I N G E E K E N T E RTA I N M E N T ™
C ++ C R A S H C O U R S E
C++ is one of the most widely used languages for real-world software. In the hands of a knowledgeable programmer, C++ can produce small, efficient, and readable code that any programmer would be proud of.
C ++
Covers C++17
CR A SH COURSE A
F A S T - P A C E D
I N T R O D U C T I O N
JOSH LOSPINOSO
C++ CRASH COURSE
C++ CRASH COURSE A Fast-Paced Introduction
b y Jos h L o s p i n o s o
San Francisco
C++ CRASH COURSE. Copyright © 2019 by Josh Lospinoso. All rights reserved. No part of this work may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording, or by any information storage or retrieval system, without the prior written permission of the copyright owner and the publisher. Printed in USA Second printing 23 22 21 20 2 3 4 5 6 7 8 9
FSC LOGO
FPO
ISBN-10: 1-59327-888-8 ISBN-13: 978-1-59327-888-5 Publisher: William Pollock Production Editors: Meg Sneeringer and Riley Hoffman Cover Illustration: Josh Ellingson Interior Design: Octopod Studios Developmental Editors: Chris Cleveland and Patrick DiJusto Technical Reviewer: Kyle Willmon Copyeditor: Anne Marie Walker Compositors: Happenstance Type-O-Rama, Riley Hoffman, and Meg Sneeringer Proofreader: Paula L. Fleming For information on distribution, translations, or bulk sales, please contact No Starch Press, Inc. directly: No Starch Press, Inc. 245 8th Street, San Francisco, CA 94103 phone: 1.415.863.9900; [email protected] www.nostarch.com Library of Congress Cataloging-in-Publication Data Names: Lospinoso, Josh, author. Title: C++ crash course : a fast-paced introduction / Josh Lospinoso. Description: First edition. | San Francisco, CA : No Starch Press, Inc., [2019] Identifiers: LCCN 2019020529 (print) | LCCN 2019022057 (ebook) | ISBN 9781593278892 (epub) | ISBN 1593278896 (epub) | ISBN 9781593278885 (print) | ISBN 1593278888 (print) Subjects: LCSH: C++ (Computer program language) | Computer programming. Classification: LCC QA76.73.C153 (ebook) | LCC QA76.73.C153 L67 2019 (print) | DDC 005.13/3--dc23 LC record available at https://lccn.loc.gov/2019020529
No Starch Press and the No Starch Press logo are registered trademarks of No Starch Press, Inc. Other product and company names mentioned herein may be the trademarks of their respective owners. Rather than use a trademark symbol with every occurrence of a trademarked name, we are using the names only in an editorial fashion and to the benefit of the trademark owner, with no intention of infringement of the trademark. The information in this book is distributed on an “As Is” basis, without warranty. While every precaution has been taken in the preparation of this work, neither the author nor No Starch Press, Inc. shall have any liability to any person or entity with respect to any loss or damage caused or alleged to be caused directly or indirectly by the information contained in it.
#include #include #include int main() { auto i{ 0x01B99644 }; std::string x{ " DFaeeillnor" }; while (i--) std::next_permutation(x.begin(), x.end()); std::cout is_sentient = true; } void make_sentient(HolmesIV& mike) { mike.is_sentient = true; } Listing 5: A program illustrating the use of the dot and arrow operators
xl An Overture to C Programmers
Under the hood, references are equivalent to pointers because they’re also a zero-overhead abstraction. The compiler produces similar code. To illustrate this, consider the results of compiling the make_sentient functions on GCC 8.3 targeting x86-64 with -O2. Listing 6 contains the assembly generated by compiling Listing 5. make_sentient(HolmesIV*): mov BYTE PTR [rdi], 1 ret make_sentient(HolmesIV&): mov BYTE PTR [rdi], 1 ret Listing 6: The assembly generated from compiling Listing 5
However, at compile time, references provide some safety over raw pointers because, generally speaking, they cannot be null. With pointers, you might add a nullptr check to be safe. For example, you might add a check to make_sentient, as in Listing 7. void make_sentient(HolmesIV* mike) { if(mike == nullptr) return; mike->is_sentient = true; } Listing 7: A refactor of make_sentient from Listing 5 so it performs a nullptr check
Such a check is unnecessary when taking a reference; however, this doesn’t mean that references are always valid. Consider the following function: HolmesIV& not_dinkum() { HolmesIV mike; return mike; }
The not_dinkum function returns a reference, which is guaranteed to be non-null. But it’s pointing to garbage memory (probably in the returnedfrom stack frame of not_dinkum). You must never do this. The result will be utter misery, also known as undefined runtime behavior: it might crash, it might give you an error, or it might do something completely unexpected. One other safety feature of references is that they can’t be reseated. In other words, once a reference is initialized, it can’t be changed to point to another memory address, as Listing 8 shows. int main() { int a = 42; int& a_ref = a; u int b = 100; a_ref = b; v } Listing 8: A program illustrating that references cannot be reseated An Overture to C Programmers xli
You declare a_ref as a reference to int a u. There is no way to reseat a_ref to point to another int. You might try to reseat a with operator= v, but this actually sets the value of a to the value of b instead of setting a_ref to reference b. After the snippet is run both a and b are equal to 100, and a_ref still points to a. Listing 9 contains equivalent code using pointers instead. int main() { int a = 42; int* a_ptr = &a; u int b = 100; *a_ptr = b; v } Listing 9: An equivalent program to Listing 8 using pointers
Here, you declare the pointer with a * instead of a & u. You assign the value of b to the memory pointed to by a_ptr v. With references, you don’t need any decoration on the left side of the equal sign. But if you omit the * in *a_ptr, the compiler would complain that you’re trying to assign an int to a pointer type. References are just pointers with extra safety precautions and a sprinkle of syntactic sugar. When you put a reference on the left side of an equal sign, you’re setting the pointed-to value equal to the right side of the equal sign.
auto Initialization C often requires you to repeat type information more than once. In C++, you can express a variable’s type information just once by utilizing the auto keyword. The compiler will know the variable’s type because it knows the type of the value being used to initialize the variable. Consider the following C++ variable initializations: int x = 42; auto y = 42;
Here, x and y are both of int type. You might be surprised to know that the compiler can deduce the type of y, but consider that 42 is an integer literal. With auto, the compiler deduces the type on the right side of the equal sign = and sets the variable’s type to the same. Because an integer literal is of int type, in this example the compiler deduces that the type of y is also an int. This doesn’t seem like much of a benefit in such a simple example, but consider initializing a variable with a function’s return value, as Listing 10 illustrates. #include struct HolmesIV { --snip-};
xlii An Overture to C Programmers
HolmesIV* make_mike(int sense_of_humor) { --snip-} int main() { auto mike = make_mike(1000); free(mike); } Listing 10: A toy program initializing a variable with the return value of a function
The auto keyword is easier to read and is more amenable to code refactoring than explicitly declaring a variable’s type. If you use auto freely while declaring a function, there will be less work to do later if you need to change the return type of make_mike. The case for auto strengthens with more complex types, such as those involved with the template-laden code of the stdlib. The auto keyword makes the compiler do all the work of type deduction for you. NOTE
You can also add const, volatile, &, and * qualifiers to auto.
Namespaces and Implicit typedef of struct, union, and enum C++ treats type tags as implicit typedef names. In C, when you want to use a struct, union, or enum, you have to assign a name to the type you’ve created using the typedef keyword. For example: typedef struct Jabberwocks { void* tulgey_wood; int is_galumphing; } Jabberwock;
In C++ land, you chortle at such code. Because the typedef keyword can be implicit, C++ allows you instead to declare the Jabberwock type like this: struct Jabberwock { void* tulgey_wood; int is_galumphing; };
This is more convenient and saves some typing. What happens if you also want to define a Jabberwock function? Well, you shouldn’t, because reusing the same name for a data type and a function is likely to cause confusion. But if you’re really committed to it, C++ allows you to declare a namespace to create different scopes for identifiers. This helps to keep user types and functions tidy, as shown in Listing 11. #include namespace Creature { u struct Jabberwock { void* tulgey_wood; int is_galumphing; An Overture to C Programmers xliii
}; } namespace Func { v void Jabberwock() { printf("Burble!"); } } Listing 11: Using namespaces to disambiguate functions and types with identical names
In this example, Jabberwock the struct and Jabberwock the function now live together in frabjous harmony. By placing each element in its own namespace— the struct in the Creature namespace u and the function in the Func namespace v–you can disambiguate which Jabberwock you mean. You can do such disambiguation in several ways. The simplest is to qualify the name with its namespace, for example: Creature::Jabberwock x; Func::Jabberwock();
You can also employ a using directive to import all the names in a namespace, so you’d no longer need to use the fully qualified element name. Listing 12 uses the Creature namespace. #include namespace Creature { struct Jabberwock { void* tulgey_wood; int is_galumphing; }; } namespace Func { void Jabberwock() { printf("Burble!"); } } using namespace Creature; u int main() { Jabberwock x; v Func::Jabberwock(); } Listing 12: Employing using namespace to refer to a type within the Creature namespace
The using namespace u enables you to omit the namespace qualification v. But you still need a qualifier on Func::Jabberwock, because it isn’t part of the Creature namespace.
xliv An Overture to C Programmers
Use of a namespace is idiomatic C++ and is a zero-overhead abstraction. Just like the rest of a type’s identifiers, the namespace is erased by the compiler when emitting assembly code. In large projects, it’s incredibly helpful for separating code in different libraries.
Intermingling C and C++ Object Files C and C++ code can coexist peacefully if you’re careful. Sometimes, it’s necessary for a C compiler to link object files emitted by a C++ compiler (and vice versa). Although this is possible, it requires a bit of work. Two issues are related to linking the files. First, the calling conventions in the C and C++ code could potentially be mismatched. For example, the protocols for how the stack and registers are set when you call a function could be different. These calling conventions are language-level mismatches and aren’t generally related to how you’ve written your functions. Second, C++ compilers emit different symbols than C compilers do. Sometimes the linker must identify an object by name. C++ compilers assist by decorating the object, associating a string called a decorated name with the object. Because of function overloads, calling conventions, and namespace usage, the compiler must encode additional information about a function beyond just its name through decoration. This is done to ensure that the linker can uniquely identify the function. Unfortunately, there is no standard for how this decoration occurs in C++ (which is why you should use the same tool chain and settings when linking between translation units). C linkers know nothing about C++ name decoration, which can cause problems if decoration isn’t suppressed whenever you link against C code within C++ (and vice versa). The fix is simple. You wrap the code you want to compile with C-style linkages using the statement extern "C", as in Listing 13. // header.h #ifdef __cplusplus extern "C" { #endif void extract_arkenstone(); struct MistyMountains { int goblin_count; }; #ifdef __cplusplus } #endif Listing 13: Employing C-style linkage
This header can be shared between C and C++ code. It works because __cplusplus is a special identifier that the C++ compiler defines (but the C
compiler doesn’t). Accordingly, the C compiler sees the code in Listing 14 after preprocessing completes. Listing 14 illustrates the code that remains.
An Overture to C Programmers xlv
void extract_arkenstone(); struct MistyMountains { int goblin_count; }; Listing 14: The code remaining after the preprocessor processes Listing 13 in a C environment
This is just a simple C header. The code between the #ifdef __cplusplus statements is removed during preprocessing, so the extern "C" wrapper isn’t visible. For the C++ compiler, __cplusplus is defined in header.h, so it sees the contents of Listing 15. extern "C" { void extract_arkenstone(); struct MistyMountains { int goblin_count; }; } Listing 15: The code remaining after the preprocessor processes Listing 13 in a C++ environment
Both extract_arkenstone and MistyMountains are now wrapped with extern "C", so the compiler knows to use C linkage. Now your C source can call into compiled C++ code, and your C++ source can call into compiled C code.
C++ Themes This section takes you on a brief tour of some core themes that make C++ the premier system-programming language. Don’t worry too much about the details. The point of the following subsections is to whet your appetite.
Expressing Ideas Concisely and Reusing Code Well-crafted C++ code has an elegant, compact quality. Consider the evolution from ANSI-C to modern C++ in the following simple operation: looping over some array v with n elements, as Listing 16 illustrates. #include int main() { const size_t n{ 100 }; int v[n]; // ANSI-C size_t i; for (i=0; i