c++ coding guide

50
C++ Advice and Information CSCI 104 Teaching Team January 31, 2014

Upload: chrislee

Post on 06-Feb-2016

265 views

Category:

Documents


2 download

DESCRIPTION

C++ Coding Guide

TRANSCRIPT

Page 1: C++ Coding Guide

C++ Advice and Information

CSCI 104 Teaching Team

January 31, 2014

Page 2: C++ Coding Guide

Abstract

This document is a collection of useful bits of information related to program-ming tasks you will encounter throughout the semester. You will be workingin C++, one of the most powerful and challenging modern programming lan-guages. It is our hope that the information here will make it easier to understandthe mistakes you make and eventually to avoid making them altogether.

Throughout the guide, helpful information is marked by , style suggestions

are marked by , C++11 specific information by , and other information

by .

Page 3: C++ Coding Guide

Contents

1 Useful Background Information 31.1 Incredibly Useful Computer Basics . . . . . . . . . . . . . . . . . 3

1.1.1 Number Systems . . . . . . . . . . . . . . . . . . . . . . . 31.1.2 Counting . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

1.2 Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.2.1 Endianness . . . . . . . . . . . . . . . . . . . . . . . . . . 5

1.3 The Compiler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61.3.1 Step One: The Pre-Processor . . . . . . . . . . . . . . . . 71.3.2 Step Two: The Compiler . . . . . . . . . . . . . . . . . . 71.3.3 Step Three: The Assembler . . . . . . . . . . . . . . . . . 81.3.4 Step Four: The Linker . . . . . . . . . . . . . . . . . . . . 8

2 C++ Coding Tips and Style 102.1 C++ Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102.2 The Pre-Processor . . . . . . . . . . . . . . . . . . . . . . . . . . 10

2.2.1 Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112.2.2 Include Guards . . . . . . . . . . . . . . . . . . . . . . . . 12

2.3 Namespaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132.3.1 The using keyword . . . . . . . . . . . . . . . . . . . . . . 14

2.4 Classes and Structs . . . . . . . . . . . . . . . . . . . . . . . . . . 152.4.1 Access Control . . . . . . . . . . . . . . . . . . . . . . . . 152.4.2 Friendship . . . . . . . . . . . . . . . . . . . . . . . . . . . 16

2.5 Const . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162.5.1 Constant Variables . . . . . . . . . . . . . . . . . . . . . . 172.5.2 Constant Member Functions . . . . . . . . . . . . . . . . 17

2.6 Passing by Value vs. Reference . . . . . . . . . . . . . . . . . . . 182.6.1 Passing by Value . . . . . . . . . . . . . . . . . . . . . . . 182.6.2 Passing by Reference . . . . . . . . . . . . . . . . . . . . . 19

2.7 Pre and Post-Increment and Decrement . . . . . . . . . . . . . . 202.8 Measuring Program Speed . . . . . . . . . . . . . . . . . . . . . . 21

2.8.1 Basic Method . . . . . . . . . . . . . . . . . . . . . . . . . 222.8.2 Iterative Method . . . . . . . . . . . . . . . . . . . . . . . 222.8.3 Using C++11 . . . . . . . . . . . . . . . . . . . . . . . . . 24

1

Page 4: C++ Coding Guide

3 How to Fix Your Code 253.1 C++ Compiler Error . . . . . . . . . . . . . . . . . . . . . . . . . 263.2 C++ Linker Error . . . . . . . . . . . . . . . . . . . . . . . . . . 273.3 Run-Time Error . . . . . . . . . . . . . . . . . . . . . . . . . . . 31

3.3.1 Basic Debugging . . . . . . . . . . . . . . . . . . . . . . . 323.3.2 Advanced Debugging . . . . . . . . . . . . . . . . . . . . . 32

3.4 Logic Error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343.5 References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

4 Collected Questions and Answers 354.1 Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

4.1.1 Linker Error . . . . . . . . . . . . . . . . . . . . . . . . . 354.2 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36

4.2.1 Throwing a Runtime Exception . . . . . . . . . . . . . . . 364.2.2 Exceptions in While Loops . . . . . . . . . . . . . . . . . 38

4.3 References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394.3.1 Passing a Pointer by Reference into a Function . . . . . . 394.3.2 Member Variable is Private . . . . . . . . . . . . . . . . . 40

4.4 Virtual Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . 414.4.1 Constructors and Virtual . . . . . . . . . . . . . . . . . . 41

4.5 Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424.5.1 General Iterator Confusion . . . . . . . . . . . . . . . . . 42

4.6 Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444.6.1 Initialization Lists . . . . . . . . . . . . . . . . . . . . . . 444.6.2 Is-a string . . . . . . . . . . . . . . . . . . . . . . . . . . . 454.6.3 Protected Variables . . . . . . . . . . . . . . . . . . . . . 46

4.7 Const . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 484.7.1 error: passing ’const Wall’ as ’this’ argument of ’WallPost

Wall::get(int)’ discards qualifiers . . . . . . . . . . . . . . 48

2

Page 5: C++ Coding Guide

Chapter 1

Useful BackgroundInformation

Before you delve into advice on style or substance on C++, it is useful to buildup a small amount of knowledge about how the computer and the compiler work.This information will help you understand ’the why ’ of various errors that youwill encounter. Ultimately you understand how your computer actually works,at a very basic and high level. As you progress through your education theseideas will be clarified.

NoteDon’t get too intimidated by the information in this section - you onlyneed to be familiar with it at a very high level. You will eventually builda detailed knowledge of it as you progress through your education.

1.1 Incredibly Useful Computer Basics

This section is a high level overview of some concepts that will ultimate help youbetter understand and fix common mistakes novice C++ programmers make.You will still make mistakes, but at least you’ll be able to understand what theymean.

1.1.1 Number Systems

Computers operate on a different numerical base than what you use in youreveryday life. Since computers are ultimately implemented using transistorsand electricity, they operate on a system that considers only two values - on (1)

3

Page 6: C++ Coding Guide

and off (0). The number system you are used to thinking in has been base 10(the base referring to how high you need to count to need a new digit).

Being familiar with these number systems will benefit you greatly when youhave to reason about memory and perform debugging.

Binary

The numeral system that computers use to perform all computation is calledbinary. Binary is base 2, which means that it only has two digits: 1 and 0. Youwon’t need to know too much about binary for this course, other than knowingthat eventually everything you do on your computer makes its way down tobinary. Your computer does all of its arithmetic, represents the characters youare reading, and even the colors of your screen in binary. Some example binarynumbers: 0b1 is equivalent to 1 (base 10, assume if no prefix the number is base10), 0b1001 is 9, and 0b10 is 2.

Hexadecimal

Also known as hex, this is a base 16 numeral system, where the digits A, B, C,D, E, and F represent the base 10 values 10, 11, 12, 13, 14, and 15. Hexadecimalis usually written with a 0x prefix to the numbers, e.g. 0x10 is the number 16in base 10.

1.1.2 Counting

In C++ counting always beings at zero, not at one. So the first index of some-thing like an array is at location zero, while the second item is at location one.The why for this has to do with memory addresses, which we will go over brieflyin the next section.

1.2 Memory

Your computer stores information in a hierarchy of components, with the CPUbeing at the top, and disk being at the bottom. As you move up the hierarchy,the amount of space gets smaller but the speed gets much faster. For this class,all you really need to know is that somewhere along the line your computerstores information in memory.

You can think of memory as a big array (solid contiguous block) of informa-tion (1s and 0s). Since everything on the computer is ultimately representedin binary, it can all be stored in memory. Since counting in binary is tedious,

4

Page 7: C++ Coding Guide

memory addresses are most often represented by a hexadecimal (base 16) num-ber. One hexadecimal digit represents four binary digits, e.g. 0b1010 (10) is0xA.

When talking about memory, we call one binary digit one bit. When we haveenough bits, we call it a byte. The number of bits in a byte is dependentupon your computer architecture, but for this class you can safely assume thatthis number is 8 (eight bits in one byte). We can represent a byte using twohexadecimal numbers, e.g. 0x31 is 0b00110001. A byte is generally the smallestunit of information your computer can allocate.

We can now think of memory as a large array of bytes, where the first location(one byte or 8 bits) in memory is addressed by 0x0, and the second address by0x1 (see section 1.1.2 on counting).

Useful InformationInformation is stored in memory as a series if 1s and 0s known as bits. Itgenerally takes 8 bits to represent 1 byte, the smallest unit of allocationon a computer. You should think of memory as an array of these bytes,with the first entry indexed by memory address 0x0.

1.2.1 Endianness

NoteUnless you are serializing data in a binary format (reading or writing fromdisk in binary, or over the network), you generally don’t need to worryabout endianness.

If we want to represent some number that is too big to fit into a byte (its binaryrepresentation would take more digits than a byte can hold), how would we dothat? Let’s consider the number 256, which is one more than the maximumnumber we can represent using 8 bits, or 1 byte. The hexadecimal for thisnumber is 0x100. However, when we store things on the computer, we needto fit them into the smallest units of allocation, bytes. This number is threehexadecimal digits, so the smallest amount of space we can fit it in is two bytes,which would be four hexadecimal digits: 0x0100 (note the leading zero). Wecall the last byte we needed to add to represent the number the most significantbit (or byte, usually written as MSB).

When the computer puts a number like 0x0100 into memory, it has two choicesfor how it can order the bytes: big-endian, and little-endian:

5

Page 8: C++ Coding Guide

Big Endian

In big-endian encoding, the MSB is ordered first in memory, so if we were totake the number 256 and represent it in this fashion, at memory address zero(0x0) we would have the MSB (0x01), and at memory address one (0x1) wewould have the next most set of significant bits, which in this case is just theremainder of the number (0x00). So the memory would be laid out like: 01 00,with 01 being at the lowest memory address.

Little Endian

In little-endian encoding, the MSB is ordered highest in memory, meaning thatthe LSB (least significant bit) comes first. For our example of 256, this wouldput the 0x00 portion first, followed by the 0x01 portion. So the memory layoutwould look like: 00 01, with 00 being at the lowest memory address.

Confused? Check out the Wikipedia article1 with pretty pictures!

1.3 The Compiler

The compiler is the magic box (it’s actually a program) that turns the code youwrite into a program your computer can actually execute. Knowing a little bitabout the compiler, namely the stages of compilation, will help you write goodC++ code.

There are two types of files you can write for the compiler: header files andsource files:

Header files (usually .h, .hpp, or .H) are files that are #included in otherfiles. In general you should never include source files and only include headerfiles. The file extension doesn’t matter other than matching coding conventions- stick to .h, .hpp, or .H for your C++ header files. Header files are neverdirectly compiled themselves, they are only compiled after being included intosource files.

Source files (usually .c, .cpp, or .C) are files that are compiled by the com-piler. What this means exactly will be detailed in the next few sections. Sourcefiles are almost never included in other source files.

What happens when you compile a program with something like g++ -std=c++11

myfile.cpp main.cpp -o myprogram? The compiler will hopefully end up cre-

1http://en.wikipedia.org/wiki/Endianness

6

Page 9: C++ Coding Guide

ating a program called myprogram if there are no errors, but how does it get tothis step? There are four steps to compilation2:

1. The pre-processor

2. The compiler

3. The assembler

4. The linker

When you compile a program, it goes through stages 1 to 3 for every sourcefile you tell it about. Stage 4 only happens once after all source files havealready gone through stages 1 through 3. This means that for the previousexample, both myfile.cpp and main.cpp will go through steps 1 through 3independently, and then be combined together into your program in step 4.The individual files are referred to as translation units or compilation units.

Useful InformationRemember that each source file goes through steps 1 through 3 indepen-dently and that they are only combined through the linker in step 4 onceall source files have successfully made it through step 3. We call files thathave made it through step 3 compiled. Each file, during its compilationprocess, is referred to as a translation unit or compilation unit.

1.3.1 Step One: The Pre-Processor

The pre-processor is the first stage of compilation and operates on any line thatbegins with a pound/hash sign (#). Depending on the command following the#, the pre-processor will do some action. The important thing to remember isthat at its core, the pre-processor can only cut, copy, and paste text.

Whenever you type #include <something.h>, the entire contents of something.hare copy and pasted to that location in your file. If you use macros such as#define SQUARE(x) x*x, these will be evaluated by copying and pasting argu-ments at the text level.

The pre-processor operates directly on the written source code and finishescopying and pasting before the compiler even runs. More detail on this can befound in the C++ pre-processor section (section 2.2).

1.3.2 Step Two: The Compiler

After the pre-processor has performed its textual substitutions, the compileris run. The compiler is a program that will take your human readable source

2http://faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html

7

Page 10: C++ Coding Guide

code and turn it into significantly less readable assembly code. Assembly codeis the lowest level of instruction that is still human readable and maps directlyto binary instructions the CPU can execute.

The compiler will perform a large amount of error checking to help ensure thatthe program you write is valid and will behave as you expect it to. Compilerscan generate two kinds of diagnostic messages: warnings and errors.

Warnings are emitted for code that could potentially cause problems but is notguaranteed to result in an issue. It’s a good idea to try and get your codeto compile with no warnings, as these often eventually manifest into run timeor logic errors. Try compiling with all warnings turned on (-Wall) as well aswarnings as errors (-Werror) if you want to avoid letting warnings pass through.

The other diagnostic is a compiler error. This is usually some syntax error youhave made that is not legal C++. The compiler will do its best to tell you whatyou did wrong, but learning to decipher compiler messages is something of anart you will have to slowly learn through experience. Take a look at section 3for some help on debugging and fixing errors.

1.3.3 Step Three: The Assembler

The assembler is a step that will take the file generated by the compilation stage(usually a .s file), which is written in assembly code, and generate object code,which is in a binary format the CPU can directly understand.

Once a file has passed through this stage, it is considered to be fully compiled.All that remains is for it to be linked in the last stage. Remember that eachsource file proceeds up to this point independently.

1.3.4 Step Four: The Linker

Once every single source file you told the compiler about has been fully compiled,it is the linkers job to combine them all together and create the final executableprogram. The linker operates on all files simultaneously unlike the precedingthree steps which each operated on a single file in isolation. The purpose of thelinker is to match up function calls and variables that span multiple translationunits (source files). The linker does this by using a system of imports and exportsof function/variable names.

imports are any function calls or external variables you use that do not havelocal implementations (in the same source file/translation unit). These are puton a ’import list’ that is a request of names that other translation units mustsupply in order for the linking to be successful.

8

Page 11: C++ Coding Guide

exports are any globally visible (global scope) functions or variables. Whena single source file is being compiled, any globally visible function or variable,after performing all #include and other pre-processor commands, will be givento the linker as a list of so-called exported names. These exported names canthen be matched to import requests in other translation units.

If the linker cannot match every import to an available exported name, you willget a translation error. Translation errors are difficult to read and usually men-tion undefined references. These undefined references are the missing exportednames.

9

Page 12: C++ Coding Guide

Chapter 2

C++ Coding Tips and Style

This chapter covers some standards for styling your C++ code and some generaltips for programming in C++. You should definitely read chapter 4 before divingin.

2.1 C++ Reference

One of the best resources for information on C++ is cppreference.com.1 Youcan find language information2 as well as standard library information3. A goodsecondary website is cplusplus.com4

Remember that while you are allowed to seek information on the internet anddiscuss problems with classmates, the solutions you ultimately present must beentirely your own. Do not take code you find online. If you do read code online,sleep on it before implementing it yourself, and do it from scratch. It is veryeasy for us to detect code that looks out of place or is taken from the internetor other students.

2.2 The Pre-Processor

The pre-processor is part of the C++ compiler that runs before any code iscompiled. Pre-processor directives are prefixed with the hash symbol (#) andinclude things such as macros, includes, and compiler specific directives.

1http://en.cppreference.com2http://en.cppreference.com/w/cpp/language3http://en.cppreference.com/w/cpp4http://www.cplusplus.com/

10

Page 13: C++ Coding Guide

2.2.1 Macros

Macros are ways of performing textual substitution inline in your code. Theseare much more commonly used in C than in C++ where templates and inlinefunctions perform many of the same duties. Because these perform pure textualsubstitution, they can lead to logic errors that can be hard to spot:

Figure 2.1: Macro Pitfall

#define SQUARED(x) x*x

// in C++03 and earlier:

inline int squared(int x) { return x*x; }

// in C++11 and later,

// mark as a constant expression

inline constexpr int squared11(int x) { return x*x; }

int main()

{

int y = 2;

// macros perform raw textual substitution

int y_squared = SQUARED(y++);

// what is the value of y now?

// what is the value of y_squared?

int z = 2;

// inline functions will often be optimized away

int z_squared = squared(z++);

// constexpr can happen at compile time

int z_squared2 = squared11(z++);

return 0;

}

Style GuideAvoid the use of pre-processor macros unless absolutely necessary. UseC++ idioms such as inline functions, templates, and constexpr (C++11)instead.

11

Page 14: C++ Coding Guide

C++11 TipIn C++11, there is a new keyword constexpra that denotes functions thatare capable of running at compile time, which you should use wheneverpossible.

ahttp://en.cppreference.com/w/cpp/language/constexpr

2.2.2 Include Guards

C++ lacks a module system and all files included using #include are effectivelypasted where the #include directive is, meaning that multiple #includes for thesame file will cause it to be included several times, leading to naming conflicts.

The way to circumvent this is by using include guards, which is a name forpre-processor directives designed to detect a file has already been included andprevent it from being processed more than once in the same compilation unit.

These include guards need to be placed in every file that is intended to be#included from another one (such as headers). There are two ways of doingthis:

1. Using #ifndef, #define, and #endif:

// use some unique name to idenfity the file

// usually the path from your project

// base directory and the file name

#ifndef PATH_TO_MYFILE_MYFILE_HPP_

#define PATH_TO_MYFILE_MYFILE_HPP_

// actual code goes here

#endif // PATH_TO_MYFILE_MYFILE_HPP_

The pre-processor will only allow the code to be compiled if the token is notdefined. Including the file will cause the token to be defined, preventinga subsequent inclusion from causing the code to be duplicated.

2. Using #pragma once:

#pragma once

// actual code goes here

This is a much more succinct but is not as portable as the previous method;check with your compiler to see if this works.

12

Page 15: C++ Coding Guide

Style GuidePrefer the use of the first solution (#ifndef, #define, and #endif) sinceit is more portable.

2.3 Namespaces

The first place that you will likely see namespaces is the C++ standard tem-plate library (STL), which uses the namespace std. Namespaces are a way oforganizing similar code into named hierarchies.

It is a good idea to keep all of the implementation code you write in its ownnamespace and use nested namespaces to organize things as necessary. Names-paces help avoid naming collisions and give an identity to the code they contain.

Namespaces only need to be explicitly stated when accessing something whileoutside of the scope of that namespace. If you are working inside of your ownnamespace, you can omit it while referencing other entities. The compiler willsearch all namespaces, starting with the most nested one:

Figure 2.2: Namespace examples

namespace // anonymous namespace

{ int x; }

namespace mycode

{ int x; }

int main()

{

int y = x; // refers to anonymous x

int z = mycode::x; // access mycode x

return 0;

}

You can also declare anonymous namespaces by opening a namespace with noname. These can only be accessed from the same scope in which they weredeclared with no access specifier needed (see figure 2.2 for an example). Anony-mous namespaces can also be used to hide declarations from other files if theyare #included.

Style GuideKeep your code in its own namespace and use anonymous namespaces forlocal functions that don’t need to be seen elsewhere.

13

Page 16: C++ Coding Guide

2.3.1 The using keyword

The keyword using is used to bring a namespace into the current scope, meaningthat you can use it as if you were already inside of it. This also means thatit is possible to accidentally pollute namespaces by performing using in globalscopes, so you should only use using in either local scopes or in source files thatwill not be #included in any other file.

Figure 2.3: using namespace examples

#include <vector>

namespace mycode

{ int x; }

int main()

{

// bring std namespace into this scope

using namespace std;

// std namespace has been brought into scope

vector<int> myVector;

// can still use explicitly

std::vector<bool> myBoolVector;

{

// using is always local to current scope

using namespace mcode;

int z = x;

}

// scope expired, using does not cover here

int w = mycode::x;

return 0;

}

One major disadvantage to using is that it makes it harder to understand whatis happening when looking over code. In the case of long namespaces, these canbe aliased to a shorter name by using namespace aliases5 (Figure 2.4).

The same rules for scoping apply for alias - it is only valid for the scope it isperformed in and should not be done anywhere where it can pollute the globalscope.

5http://en.cppreference.com/w/cpp/language/namespace_alias

14

Page 17: C++ Coding Guide

Figure 2.4: namespace aliasing

namespace long_name_1

{

namespace long_name_2

{ int x; }

}

int main()

{

namespace short=long_name_1::long_name_2;

return short::x;

}

Style GuideOnly use using in a local scope or file that will not be #included byanother file (e.g. keep it inside a .cpp). Prefer to avoid using using tokeep code more readable.

2.4 Classes and Structs

Classes and structs are identical in C++ aside from the default visibility of theirmembers (member functions and variables). Classes are private by default, andstructs are public by default. In idiomatic C++, structs are generally usedfor lower-level containers where access control is less important. Classes aretypically used when it matters what data users have access to and is generallyused with anything that is more complicated than a collection of variables.

2.4.1 Access Control

There are three types of access control for classes and structs:

1. public - access is available to anyone

2. protected - access is only available within the class and to derived children

3. private - access is only available within the class

When you inherit from another class, you can specify an access control to beapplied to the entire class inherited, which follows rules as seen in figure 2.1.

15

Page 18: C++ Coding Guide

Table 2.1: Access Control During Inheritance

Parent Access Declaration Inheritance Access Declaration Access as seen by derived classpublic public publicpublic protected protectedpublic private privateprotected public protectedprotected protected protectedprotected private privateprivate public privateprivate protected privateprivate private private

2.4.2 Friendship

In C++, you can get around the access requirements of a class by declaringfriend classes. Once you declare some other class or function as a friend, it seesall of the data members of the declaring class as public, regardless of what theyactually are. This is especially useful when working with iterators, which weoften want to conceal the true implementation of a class while allowing easytraversal through data.

The syntax for declaring a friend is identical to the syntax for performing anormal declaration (when you say what something is but don’t define it), exceptthat you prefix it with the keyword friend:

template <class T>

class MyClass

{

// declare a function as a friend

friend void printClass( MyClass & );

// declare some other class as a friend

friend struct AnotherStruct;

// declare some templated class as a friend

// note we don’t re-use T here

template <class U> friend class TemplatedClass;

};

2.5 Const

When something is constant, it means that it cannot be changed. There aretwo big ways that this is used in C++, the first is to make a variable constant,

16

Page 19: C++ Coding Guide

and the second is to make a class member function constant.

2.5.1 Constant Variables

When a variable is constant, it means that it cannot change its value. This isuseful because it can prevent you (the programmer) from accidentally changinga value that should not be changed. It can also let the compiler perform specialoptimizations since it knows that a value will stay constant. Your attitudetowards using the const keyword should be to apply it to everything, and treatcases that cannot be made constant as exceptions. In addition to the benefitsalready listed, many actual libraries and data structures implemented in C++offer improved performance when operating in a constant fashion.

const int x = 3; // value can not be changed

const int * y = &x; // what we point to can’t be changed,

// although we can change the pointer itself

const int * const y = &x; // both what we point to and the pointer

// itself are constant

Style GuideTry to make every variable (including parameters) const unless you havea reason to let it stay mutable. Also try to apply this philosophy to classmember functions.

2.5.2 Constant Member Functions

Another important way const can be used is to make a member function of aclass or a struct constant. When used in this context, the constancy appliesto any member variables used within that function. The const applied to thefunction says that you will not change the value of any class member variableinside of that function. You can still change local variables inside the function(that are not themselves const), but you will be unable to change the value ofany member variables.

struct Struct

{

int doSomething() const

{

// can still change local variables

int localVariable = 2;

localVariable += 1

// not allowed to change _memberInt

return _memberInt + 3;

17

Page 20: C++ Coding Guide

}

void increment()

{

// not const, can change member variables

++_memberInt;

}

int _memberInt;

};

Style GuideIf a class member function does not need to change any member variables,mark it const.

2.6 Passing by Value vs. Reference

When writing a function, you have two choices on how to construct the argu-ments. Arguments can accept parameters by either value or reference (as ofC++11 you’d actually have three choices, but you don’t need to worry aboutthat for this course).

2.6.1 Passing by Value

When you have an argument that accepts a parameter by value, it performs acopy of the data when it is passed in. Something is considered to be passed byvalue if there is no reference (no & symbol) in the declaration.

void myFunc( int paramOne, bool * paramTwo, std::vector<double> paramThree );

// all of the above parameters are done by value, since there is no & present

Note that since passing by value makes a copy, it can be extremely inefficient ifyou do not need to make this copy. Consider a function that sets some membervariable in a class:

struct MyStruct

{

std::vector<double> x;

void setX( std::vector<double> xx ) // copy #1

{

x = xx; // copy #2

}

};

18

Page 21: C++ Coding Guide

MyStruct s;

std::vector<double> vec(1000);

s.setX( vec );

In the above example we want to set x in the struct to equal vec. If we pass byvalue here, we copy the entire vector (and all of its 1000 elements) two times- first when we pass it to the function, and then again when we assign it tothe member variable. Ideally in this situation, since we are not doing anythingto the parameter in the function, we would like to avoid this extra copy andperformance hit. This is where passing by reference (specifically const reference)helps us.

Style GuideIf you will be changing the value of a parameter, or if it is smaller thanthe size (sizeof) a pointer, feel free to pass it by value.

C++11 TipIn C++11 there is extra optimization that can sometimes occur wherevalued are moved rather than being copied in situations like the one pre-sented above. This is an advanced topic that is outside the scope of thecourse.

2.6.2 Passing by Reference

When we pass a variable by reference, we are essentially passing a pointer tothat variable instead of making a copy of it. This is advantageous as soon asthe size of the object becomes larger than the size of a pointer, which will betrue for most structs and classes. References give us the flexibility of pointerswithout having to delve into pointer syntax. When we change a reference, itchanges the original value:

struct MyStruct

{

std::vector<double> x;

void setX( std::vector<double> & xx ) // no copy, using reference

{

xx.push_back( 32.2 ); // changes the original vector

x = xx; // copy #1 (includes the insertion we made)

}

};

MyStruct s;

std::vector<double> vec(1000);

s.setX( vec );

// vec is now size 1001

19

Page 22: C++ Coding Guide

In the above example, we have eliminated an extra copy that occurred when wepassed the vector by value. Since we passed by a reference, any change we maketo the parameter also changes the original vector we passed in. If we don’t needto change the value of the parameter, and want to avoid making an extra copy,we should use a const reference:

struct MyStruct

{

std::vector<double> x;

void setX( std::vector<double> const & xx ) // no copy, using const reference

{

// cannot modify xx as it is const

x = xx; // copy #1

}

};

MyStruct s;

std::vector<double> vec(1000);

s.setX( vec );

// vec is unchanged

Style GuideIf you will not be changing the value of an argument and it is larger(sizeof) than a pointer, pass it by const reference.

2.7 Pre and Post-Increment and Decrement

Two very common operators that you will see used in C++ code are the incre-ment and decrement operators (++ and - -, respectively). For basic data types,these operators either increment the value by one or decrement the value byone. For user-defined types, such as iterators, the behavior is usually the samebut may be slightly different.

Regardless of whether you are using these operators on a basic type or a class,it is important to know the difference between putting the operator before (pre)and after (post) a variable.

When you use pre-increment, the variable is changed immediately, and no copyis made:

int x = 3;

int y = ++x;

// y = 4, x = 4

When you use post-increment, the variable is not changed until after the entirestatement is executed:

20

Page 23: C++ Coding Guide

int x = 3;

int y = x++;

// y = 3, x = 4

In order for this post-increment to happen, a copy of the variable is created andused in its place so that the original value can be changed without affectingthe behavior of the current statement. This means that there is more overheadassociated with the post-increment operator in addition to its unconventionalbehavior of delaying the increment. You should only use post-increment whenyou desire this special behavior, and use pre-increment (or decrement) in everyother circumstance.

Style GuideAlways use pre-increment (or decrement) unless you desire the specificbehavior that post-increment (decrement) gives you.

2.8 Measuring Program Speed

Throughout the semester, you will be learning about theoretical analysis ofthe speed of various algorithms and the impact of data structures. It is veryuseful and illuminating to complement the theoretical analysis with practicalmeasurements. Here, we point you to some simple techniques for measuringhow long a program takes.

The basic idea behind any time measurement is a stopwatch. You record theCPU time at the beginning of your operation and again at the end. ModernCPUs have a fast clock so you can measure any operation that is at least 1millisecond. In Section 2.8.2, we show you how to measure faster operations(and improve the precision even on slower measurements).

It is worth mentioning that the measured time is not purely from your program.Most Operating Systems tend to schedule and run many tasks simultaneously.Therefore it is recommended to check if another program is extensively consum-ing your CPU in the background. Also it is common practice to only measurethe processing time of the program and not the I/O portion, because that isoften much slower, and not indicative of the data structure or algorithm.

Besides the usual C++ input/output libraries, in this guide we need the time

library as well. This include file has many useful classes to store time. In orderto familiarize yourself with this library check out http://www.cplusplus.com/reference/ctime/.

21

Page 24: C++ Coding Guide

2.8.1 Basic Method

In order to programmatically measure the elapsed time, we call the clock()

function once at the beginning and again after the process is done. This functionreturns the CPU clock which later can be interpreted as seconds. The code infigure 2.5 demonstrates the procedure6.

Figure 2.5: Basic Time Measurement

#include <iostream>

#include <ctime>

using namespace std;

int main()

{

clock_t start, finish;

double dur;

start = clock();

// do something slow...

finish = clock();

// Measure the elapsed CPU time.

dur = (double)(finish - start);

dur /= CLOCKS_PER_SEC;

cout << "Elapsed seconds: "

<< scientific << dur << endl;

return 0;

}

The code in Figure 2.5 outputs the time in scientific format. Note that only theslow operation (line 12) is being measured and not the entire program.

2.8.2 Iterative Method

As you may have noticed if you implemented the code above, the reported timesare not always the same for the same operation. Additionally, many operationsare too fast to be measurable at all like this. To overcome this challenge wecan repeat the same operation and then report the average elapsed time. Thistends to give a more accurate measurement.

The code in Figure 2.6 demonstrates how we can measure more accurately. Weare repeating the same operation 1000 times (or choose a larger constant if the

6Taken from here.

22

Page 25: C++ Coding Guide

Figure 2.6: Iterative Time Measurement

#include <iostream>

#include <ctime>

using namespace std;

int main()

{

const int ITERATE = 1000;

clock_t start, finish;

double dur;

start = clock();

for (int i = 0; i < ITERATE; i++)

{

// do something not so slow...

}

finish = clock();

dur = (double)(finish - start);

dur /= (CLOCKS_PER_SEC * ITERATE);

cout << "Elapsed seconds: "

<< scientific << dur << endl;

return 0;

}

23

Page 26: C++ Coding Guide

Figure 2.7: C++11 Time Measurement

#include <chrono>

#include <iostream>

int main()

{

// get the current time

auto start =

std::chrono::high_resolution_clock::now();

for (int i = 0; i < ITERATE; i++)

{

// do something not so slow...

}

auto end = std::chrono::high_resolution_clock::now();

// output the elapsed time

std::cout << "Elapsed seconds: " <<

std::duration_cast<std::chrono::seconds>(

end - start ).count()

<< std::endl;

// we could also duration_cast this to

// milliseconds or anything else

return 0;

}

operation is very fast). At the end, we divide the total duration by 1000. Ofcourse we are also measuring the for loop itself but that is usually negligible.

2.8.3 Using C++11

If you are using C++11, you can easily measure time using functionality builtinto the standard library in the <chrono>7 header. Take a look at the code infigure 2.7 for an example of this.

C++11 TipC++11 has a high resolution clocka that requires no external librariesand will work on any Operating System (OS) with a compliant C++11compiler.

ahttp://en.cppreference.com/w/cpp/chrono/high_resolution_clock/now

7http://en.cppreference.com/w/cpp/header/chrono

24

Page 27: C++ Coding Guide

Chapter 3

How to Fix Your Code

This section covers a systematic method to read, understand, and fix the com-mon types of code errors. By now you should have learned how to setup yourC++ programming environment. More specifically, you should know how touse a professional IDE to edit code, build it, and debug the executable. Thereare four types of errors a C++ programmer may encounter:

• Compile Error: Also called syntactical error is when the compiler lit-erally does not understand what you typed. It’s like a spelling error inhuman language.

• Link Error: Is when the code is understood but the linker cannot findeverything it needs. It’s like a sentence without a verb.

• Run-Time Error: Once your binary (executable) is produced, your pro-gram can run. If it crashes, goes into infinite loop, or becomes unrespon-sive, you’re having a run-time error.

• Logic Error: Is when the program runs but does not produce the ex-pected output.

To reach enlightenment (as a programmer of course) first you have to understandthese errors. Then you can fix them. In order to fix any of these errors, youhave to follow the Four Noble Truths1 of code fixing:

Pillar 1: The truth of bug: There is suffering and sleepless nights until thecode is complete. This is the first step toward enlightenment so don’tbe sad.

Pillar 2: The truth of the origin of bug: The first thing you do is to isolatethe error. Find which part of the code is causing the bug. For eachtype of error, there are tools to help you isolate the problem.

1http://en.wikipedia.org/wiki/Four_Noble_Truths

25

Page 28: C++ Coding Guide

Pillar 3: The truth of the cessation of bug: After isolating the bug, askyourself why this is happening. Once you discovered the reason be-hind the bug, write a testing unit (function) that can deterministicallyreproduce the bug in an isolated environment.

Pillar 4: The truth of the path leading to the cessation of bug: Nowit’s time to fix the bug. Meditate on the logic behind the bug. A bugthat is fully understood is a fixed bug.

The rest of this section briefly covers standard methods to handle coding errors.We will not cover specific errors and fixes. We rather show you the way toisolate the problem and how to look up more information about it. For obviousreasons, one cannot write a document to cover all possible coding problems.

3.1 C++ Compiler Error

When you try to build a C++ project, you are basically asking the compilerto read your code, understand it, and then produce an executable (=binary).A compiler like GCC2 does that in multiple steps: parse, compile, and link3.Once the compiler sees an error in your code, it stops advancing to the nextstep. Instead it generates lots of compile and warning error messages with exactlocation of the problem.

$ g++ code.cpp -g -o code

code.cpp: In function ‘int main()’:

code.cpp:25: ’DoublyList’ does not

name a type (first use this function)

code.cpp:25: parse error before ‘;’ token

bla bla bla

Sometimes there might be lots of these errors for a single project. But fear not,follow these steps:

Step 1: Only read the first error message

Step 2: Go to the code file with the error (pillar 2)

Step 3: Look at the line that the compiler is referring to (in our example it’scode file code.cpp line number 25)

Step 4: These types of errors are usually understandable (pillar 3). If youdon’t see the problem immediately, look at the surrounding codelines.

Step 5: If you don’t understand the error message, seek help! Even if youmanaged to fix the error, don’t jump over pillar 3.

2http://gcc.gnu.org/3GCC is a huge open-source project with around 2 million lines of code. If you like to

know about its internals this page is a good start.

26

Page 29: C++ Coding Guide

Step 6: Re-compile

Step 7: Goto step 1

Things to avoid doing:

• Once you’ve fixed the first error, don’t continue with the second one!Compile again.

• Do not undo your last edits unless the section you recently edited happensto be the faulty code.

• Don’t continue writing new code either. First fix the errors. They won’tgo away if you ignore them.

• Don’t comment out the faulty code line. If you see a spelling error in adocument do you delete it!?

3.2 C++ Linker Error

Once you’ve fixed all the compile errors, then the compiler will try to link yourmodules together. At this step you may get link errors. You will not get anylink errors until you fix all your compile errors. These are slightly harder to fixbecause the linker can’t tell you the faulty code line. Additionally, the linkererror messages are a little cryptic and very general. It will however tell you theproblematic function and/or class. The following is a common link error.

$ g++ code.cpp -g -o code

/tmp/cc2Q0kRa.o: In function ‘main’:

/tmp/cc2Q0kRa.o(.text+0x18): undefined

reference to ‘Print(int)’

code: ld returned 1 exit status

we know this is a linker error because the last line says “ld returned 1 exit sta-tus”. If you use your own makefile, you may recognize the object file (cc2Q0kRa.o).The following list summarizes some of the common mistakes that may lead toa link error:

• How you build: For example if you compile both .cpp files and .h filestogether you usually get a link error. For example this is wrong:g++ code.cpp code.h -g -o code

As a rule of thumb, never #include a .cpp file and never compile a .h

file!

• If you define a function in a .h file, you have to code it in a .cpp file unlessit’s a pure virtual function in an abstract class. No other exception.

• You have to implement all defined constructors and destructors includingthe default ones even if they are not doing anything.

27

Page 30: C++ Coding Guide

• The function signature has to exactly match the one in the header fileexcept the virtual keyword.

• Re-definition Error: If something is defined (coded) twice you get a linkerror. usually you define your modules once but if you don’t properlyguard your headers or confuse the compiler, it may look like the functionexists in two different places. You may get the same error if you put yourfunction definition in a header file, or #include your implementation filein another code or header file. Mostly you can avoid this type of errorwith a well-written makefile. Note that you can declare your functionsmultiple times and that’s OK.

• Most advanced C++ compilers will not compile your unused modulewhich means you won’t see all the link (and compile) errors until youcall/reference it. Imagine this scenario: you have just finished coding theclass A and fixed all its compiler errors. Now you write something likeA newObject; in your main function and it causes new compiler errorsabout A. Is this the variable declaration error? The answer is no!

• If you’re coding a template class, “undefined reference” link error can oc-cur even if you follow the mentioned guidelines. There are at least threeworkarounds for this; (1) rename your class implementation file into a .hfile then include it in the class header file, (2) pre-define all instances ofyour template class, and (3) implement all functions inside the class defi-nition. For example the following code will give you “undefined reference”link error.

// list.h

template <class T>

class List {

void foo();

};

// list.cpp

#include "list.h"

template <class T>

void List<T>::foo() { ... }

// main.cpp

#include "list.h"

int main() {

List<int> i;

return 0;

}

compile with:

$ g++ -g main.cpp list.cpp -o list

We will briefly explain the said three workarounds from most preferred

28

Page 31: C++ Coding Guide

method to least preferred.

1. class.cpp to classImpl.h: Code your class the way you used to.Then at the end, rename your code file into a header file. For exam-ple from classname.cpp to classnameImpl.h. Then #include yournew implementation header file at the end of the original headerfile. Remove the old #include "classname.h" from the beginningof your implementation header. Since the implementation file is nolonger a .cpp file you should not compile it anymore. The followingexample demonstrates all the necessary changes.

// list.h

template <class T>

class List {

void foo();

};

#include "listImpl.h"

// listImpl.h

template <class T>

void List<T>::foo() { ... }

// main.cpp

#include "list.h"

int main() {

List<int> i;

return 0;

}

compile with:

$ g++ -g main.cpp -o list

2. Pre-define instances: Code your class the way you used to. Atthe end, pre-define all the instances you want at the bottom of yourclassname.cpp file. The downside is that you usually don’t know inadvance which instances of your template class you may need. Thefollowing example demonstrates all the necessary changes.

// list.h

template <class T>

class List {

void foo();

};

// list.cpp

#include "list.h"

template <class T>

void List<T>::foo() { ... }

template class List<int>;

29

Page 32: C++ Coding Guide

// main.cpp

#include "list.h"

int main() {

List<int> i;

return 0;

}

compile with:

$ g++ -g main.cpp list.cpp -o list

3. Move definitions into class: Code all your functions right insidethe class definition and not in a separate code file. By doing soyou can avoid some link errors (including those caused by templateclasses) but you’re also violating every rule in the book about goodcoding practices! So only use this in emergency situations. Thefollowing example demonstrates all the necessary changes.

// list.h

template <class T>

class List {

void foo() { ... }

};

// main.cpp

#include "list.h"

int main() {

List<int> i;

return 0;

}

compile with:

$ g++ -g main.cpp -o list

• Just because you have a link error in your template class, it does not meanyou have a template class link error. It still could be one of the usual linkproblems.

• If you’re using an external library, you need to tell g++ to link in with theexternal binary.

• Don’t let a miss-configured IDE or a malfunctioning Makefile trick you.If the errors do not make sense try a simple g++ compile: g++ *.cpp -g

-o OutputName. A clean build might help too.

As mentioned before, this document is not listing all the link errors and certainlyit’s not providing a packaged solution for any of them. We believe the discussedguidelines can empower a self-motivated C++ beginner with the right toolsto fix any compiler error. As you get more advanced with C++, the ratio oftime spent on fixing compiler errors as oppose to writing new code will drop

30

Page 33: C++ Coding Guide

significantly. You will also realize link errors are the easiest to fix/avoid despitethe cryptic looking messages.

3.3 Run-Time Error

After fixing all the build issues, you can now run the program. Any error thathappens during the execution of the program is a runtime error. It usuallyleads to a crash (unexpected end of program) or unresponsiveness. Some ofusual runtime errors are:

• Memory access violation [seg fault]: Happens when the program triesto access memory that does not own. Usually happens if you don’t allocatememory (pointer), try to access freed memory, or go beyond your arraybounds.

• Memory corruption [the nightmare]: If your program successfully ac-cesses (read or write) part of memory that it should not, it will causememory corruption. Unfortunately this does not immediately crash theprogram. Sometimes it feels like the bug is moving to different places.Also sometimes gdb prints out question marks (??) instead of the trace.

• Infinite loop [where FB resides]: Happens when your algorithm doesnot deterministically terminate in a finite time. The easy cases are whenyour for-loop needs a quick fix. It can also be a recursive function with afaulty base case or a bugy iterator class. Usually the program runs out ofmemory (heap or stack) and crashes with a different error message.

• Stack overflow [yes it’s an error]: It’s when the program is out of stackmemory. A similar case is when the computer is out of heap memory.These kinds of errors are not bad by themselves. However, given thattoday’s computers have so much memory, running out of memory is a signof a problem.

• Unhandled exception [don’t (...)]: It’s when the program crashes andthrows an unhandled exception back to the OS. Your OS may print theexception error message. But gdb definitely shows all the informationattached to the exception. Don’t try to catch the exception if you don’tknow how to handle it or recover from it. Some exceptions are meant tobe thrown back to user.

• Deadlock [hardly]: Unless you’re writing system code or a multi-threadedapp, then errors like deadlock, race condition, and thread interruptions areunlikely to happen.

In section 3.1 we only focused on compiler errors and not the warnings. Inorder to see all the compiler warnings, compile your code with -Wall switch:g++ *.cpp -g -Wall -o OutputName

31

Page 34: C++ Coding Guide

It’s unlikely but sometimes warnings can become future crashes! It’s not abad idea to look over them and understand the warnings before jumping intodebugging.

3.3.1 Basic Debugging

Certainly you can re-use most of what you’ve learned with compiler errors heretoo. Make a guess about problematic section of the code, then try to narrow itdown by cout-ing the program state (variables) at different places. Also learnhow to use assert in C++. Comes handy. Most memory access problems canbe isolated with a simple assert. If you’re dealing with other types of runtimeerror or if the program is too long to debug with cout and assert, then it’sprobably a good time to learn gdb. With gdb you can isolate the problematiccode section very quickly (pillar 2).

3.3.2 Advanced Debugging

Thanks to advancements in Operating Systems and new debugging tools, it’sfairly easy to isolate the faulty code (pillar 2). However you should lower yourexpectation from the debugging tool/IDE to help you fix the problem beyondisolation4.

GDB Guide

Some of most common gdb commands are reviewed here. This is just a scratchon surface. You should put some time to master either gdb or your IDE debuggerbefore diving into serious C++ programming.

Step 1: Compile your code with -g switch. For example: g++ *.cpp -g -o

OutputName

Step 2: Instead of directly running your program (./OutputName) pass it asan argument to the debugger: gdb OutputName. This will put youin the debugger shell. It’s like an interactive terminal.

Step 3: If you want to set a breakpoint type the commandbreak ClassName::FunctionName. The debugger will pause the ex-ecution of the program at the location of breakpoints. When theprogram is paused you can inspect the stack, check the value of vari-ables, and a lot more. For example if you’re getting a seg fault ona private pointer inside a class, a good starting point is to set abreakpoint on the class constructor(s) to check if everything is beinginitialized correctly.

4There are other tools that can monitor programs for potential crashes and generate helpfulreports. You will learn about them during the semester. If you cannot wait, lookup valgrind.

32

Page 35: C++ Coding Guide

Step 4: To run the program type the command r

Step 5: The following commands are useful when the program is in pausedstate (breakpoint):print VariableName: Prints the value of the variable. You have tobe in the right scope.n: Runs the next line of code and then pause again. It’s useful toadvance the execution by one line and see the changes.s: Similar to n, runs the next line of code. If the code line is afunction call, it will step into the function instead of stepping overit.c: Continues the execution of the program. It basically takes theprogram out of the paused state until the next breakpoint.bt: Prints the stack. This is very useful because it shows all thefunction calls in the current state of the program in the order thatthey have called each other. Each line shows you a frame numberand the associated function name. When a function calls anotherone, this creates a new frame on the stack.frame n: Moves you to the nth frame in the current paused state.By moving on the stack from one frame to the other, you can examinethe local variables in each function.q: Quits the debugger shell.help: This one is self-explanatory.print VariableName=value: Changes the value of the variable.

Step 6: If debugger detects that the program is crashing, it will pause theexecution automatically. In this case, go back to previous step andexamine the program state in order to isolate the bug (pillar 2).

Step 7: Write a small testing function which, if called, can deterministicallyreproduce the bug. This is called unit testing (pillar 3).

This section briefly covered basic steps to understand a run-time crash and howto isolate the bug. There are certainly much more to gdb that this document cancover. The interested reader is encouraged to practice debugging with simpleC++ programs.

Side Note

In software development, in order to improve user experience, programmersusually like to reduce run-time problems into compile-time problems. To do so,they use unit testing and code reviewing (by software or other program-mers). Unit testing is when you write code to test another program. Whenevera program crashes, we find the cause of the crash. Then we write a function thatcan deterministically reproduce the bug. For example if you’re coding a sortfunction, a testing function would be to check the output of the sort function tomake sure the same numbers are outputted in increasing order. In future builds,

33

Page 36: C++ Coding Guide

we run all previous testing functions (units) to check the program against allpreviously known bugs. Code reviewing is when a different programmer reviewsyour code to find potential bugs. There are intelligent software packages toothat can detect common programming pitfalls from source code.

3.4 Logic Error

Going back to the definition of a logic error, a program has a logic problem if itdoes not crash but returns wrong output. There is a gray area between run-timeand logic errors. The question is do you consider a wrong output any differentthan a crash? Does throwing an exception a bad output or a crash? Whatif the program is returning a wrong output because of a memory corruption?These kind of questions are language independent and it’s more about algorithmdesign than programming.

You may think that a program that has a logic error is better than a programthat crashes. However, looking from user’s perspective, at least with a programthat crashes you know the output, if produced, is wrong. In case of a logic error,the user (and probably the programmer) may not even know that a logic bugexist and may trust the correctness of the output. In a sense you don’t evenknow if there is a bug until you check all the outputs. So even pillar 1 is notapparent. After you checked your program against the basic testing units, yourbug is hopefully reduced to a run-time or compile time error which is what weknow how to deal with.

3.5 References

• Common C++ Compiler Errors

• Compiler, Linker and Run-Time Errors

• Dealing with Compiler Errors

• GDB Tutorial

34

Page 37: C++ Coding Guide

Chapter 4

Collected Questions andAnswers

This chapter contains some useful questions and answers collected from studentspreviously taking the course.

4.1 Templates

4.1.1 Linker Error

Question

I’m getting a linker error (undefined reference to XXX) when compiling sometemplate code. Why is this?

Answer

First off I want to say that you never include source files under normal circum-stances. You only include header files.

Let’s first discuss why you got your original error: Let’s pretend you have threefiles: main.cpp, list.h, and list.cpp. Let’s pretend that list.h declares a templatelinked list class and list.cpp provides the implementation. In main.cpp, youinstantiate an instance of your list, let’s say of strings. So you have:

// main.cpp:

#include <list.h>

#include <string>

35

Page 38: C++ Coding Guide

int main() {

List<std::string> aStringList;

aStringList.push_back( "Hello, World!" );

return 0;

}

You then tell the compiler to compile this with something like: g++ main.cpp

list.cpp and it yells at you with some linker error like the one originally posted.

Why does this happen?

Each source file the compiler knows about is compiled in its own translation unitand it only knows about its own contents and the contents that other source filesexport. Source files export all globally visible variables and functions. Normallywhen you write some non templated code, it gets fully compiled since all thetypes are known and it can be exported to other translation units (other sourcefiles). When you have template code, that template is just a blueprint of whatthe compiler has to do when it gets a type. It is not until you instantiate yourtemplate by saying ”I want a List of strings” (List) that the compiler goes aheadand tries to actually generate the code where it replaces with T = std::string.

The problem here is that your main.cpp can only see things that were exportedor that are visible from header files it includes. List.cpp doesn’t know that youwant a list of strings, so it never generated code for List. You are now asking forthis code and the compiler doesn’t know how to generate it since it was neverexported and it can’t see into the other translation unit to instantiate the code.This is why you generally put template code in a header file so you can includeit elsewhere and it can be seen.

The other option, as Kaveh pointed out, is that you can keep things in a sourcefile and then tell the compiler to explicitly instantiate certain versions of yourclass, e.g. with a string. The downside to this is that if you want to use a typethat you didn’t explicitly instantiate, you are back to the problem if the linkernot knowing where the code is because the compiler can’t generate it.

4.2 Exceptions

4.2.1 Throwing a Runtime Exception

Question

I was wondering how runtime exceptions are implemented into code because thetextbook specifies that runtime exceptions should not be thrown because theclient cannot handle it.

36

Page 39: C++ Coding Guide

Answer

Exceptions are a way of signaling that a non-recoverable error has occurred insome segment of code. Without exceptions this is done by having functionsreturn error codes and then always checking these return codes against variousknown errors. This C style means that people have to always check the returncodes, which is annoying, and structure their code such that it properly cleansup depending on where the error occurs. There’s also no way to enforce thatusers of your code actually check for errors.

In C++, we can use exceptions. Exceptions can be any type, including prim-itives (int, char, double, etc) or classes/structs. You say an exception has oc-curred by using throw:

// in some function

if( index < 0 || index >= _size )

throw std::out_of_range( "Index was out of range" );

// the rest of my function

A throw statement immediately exits the current function (to be more preciseit immediately jumps to the first catch statement that matches it), similar to areturn statement. It means that no lines of code after the throw are executedin the current function. So if you have something like:

int * data = new int[1000];

throw std::exception();

delete[] data;

data will never be deleted, because that line of code cannot be reached.

Exceptions are used to signal things which the current context cannot recoverfrom - imagine the situation above where you are asking for a certain index inyour container and it is out of bounds. There is nothing your container can doabout this, the data doesn’t exist and it can’t do anything to make it exist, soit throws an exception.

So if we can’t recover from it, we throw the exception with the hope that someoneelse can detect this error and do something about it. This is what try/catchstatements are for. A try block is put around some code that could throw anexception that you can actually do something about by catching that exceptionin the catch block.

void printList()

{

size_t i = 0;

try

{

while( true )

{

37

Page 40: C++ Coding Guide

std::cout << myList.get( i++ ) << std::endl;

}

}

catch( std::out_of_range const & e )

{

// do nothing with the error other than catch it

// since we have nothing to print out here

}

}

You should only use try/catch in places where you can detect and recover froman error. If you can’t recover from it, you just let the exception keep unwindingthe stack and breaking out of functions until it reaches main. When an exceptionbreaks out of main, it will crash your program by default, which is perfectlyreasonable behavior if an error happens that nothing in your code can handle.

This section is a high level overview of some concepts that will ultimate help youbetter understand and fix common mistakes novice C++ programmers make.You will still make mistakes, but at least you’ll be able to understand what theymean.

4.2.2 Exceptions in While Loops

Question

If I throw an exception in my while loop, would it automatically break out ofthe while loop if my catch is implemented after my loop?

Answer

When you throw an exception, it will unwind the stack until it hits a try/catchblock. If it never hits a try/catch block, it will break out of main and hitthe default exception handler, whose behavior is to immediately terminate yourprogram. When I say unwind the stack, I mean the memory stack that localvariables and parameters use.

In your case, if you had a loop inside of a try/catch block, then the exceptionwould back up until the opening part of the try, and then go down to the catchblock. Any variables allocated before the exception is thrown that were on thestack will be properly discarded - anything you put on the heap and didn’t getto delete will be leaked.

Think of throwing exceptions kind of like having a return statement, exceptthat instead of going back to where you called from, they go to the closest catchthat they will fit into.

38

Page 41: C++ Coding Guide

4.3 References

4.3.1 Passing a Pointer by Reference into a Function

Question

How do I pass a pointer by reference into a function?

Answer

So I take it what you are doing is passing your head pointer to some functionand you would like that function to modify that pointer and have that persist.

There are two ways to do this, one is more idiomatic C++ and the other is moreof a C style.

Remember that when you pass something to a function, it is always passed byvalue unless you mark it as being passed by reference. Things that are passedby value are copied into the argument and will not propagate outside of thefunction.

void modify_ptr( int * ptr )

{

ptr = &something;

}

Will not change the value of ptr outside of modify ptr. However, we couldchoose to pass by reference (C++ style) if we want to change this:

void modify_ptr( int * & ptr )

{

ptr = &something;

}

This will make the change to ptr actually change the value used when callingthe function.

You could also do this in a more C style by passing a pointer to your pointer:

void modify_ptr( int ** ptr )

{

*ptr = &something;

}

int * my_ptr;

modify_ptr( &my_ptr );

39

Page 42: C++ Coding Guide

4.3.2 Member Variable is Private

Question

Since my listElement’s next pointer is a private variable, how do I access it inmy LinkedList’s insert and remove functions? I tried using a getNext() functionin listElement, but that didn’t seem to work. The error is that ”lvalue requiredas left operand of assignment”

template <class T>

listElement <T> * listElement<T>::getNext()

{

return next;

}

template <class T>

void LinkedList<T>::insert(int position, const T & data)

{

// some code

listElement <T> *newPtr = new listElement <T>;

// more code

newPtr->getNext() = tail;

// more code

}

Answer

Let’s first discuss the error you were getting: ”lvalue required as left operandof assignment.” This is saying that you need a left hand value on the left handside of an assignment - helpful right? What this means in this context is thatyou were doing something like:

someListElementPtr->getNext() = someOtherListElementPtr;

This doesn’t work because the left hand side of this assignment is actually atemporary value with no name - your function returns a pointer, which youdon’t assign into any variable, and then you try to assign something to thatvariable. What happens immediately after this assignment? You just assignedto something that is in the process of evaporating into thin air. What youprobably wanted to do was to actually change the next pointer. To do this youneed to either return a pointer to the thing you want to change, or more easily,return a reference:

listElement <T> * & listElement<T>::getNext();

This now says: return a reference to a listElement¡T¿ pointer. References referto things with actual names, which means they can be assigned to. The whole

40

Page 43: C++ Coding Guide

lvalue/rvalue is a bit of an advanced topic, but this should help you understandwhy you got that error.

Now on to actually coming up with a good solution to this: one way is to dowhat I just mentioned - return a reference to your pointer instead of the pointeritself. If you do this, any changes you make to the value returned will changethe pointer in your object.

Another way to solve this is to use a friend declaration. Friend declarations are away of letting C++ know that some classes/functions are allowed to ignore yourcareful public/protected/private access control, and see everything as public. Afriend declaration would go in the class you want to open up to another class,something like:

template <class T>

class ListElement

{

public:

template <class U> friend class LinkedList;

// the rest of your class

};

What this says is that ListElement will be friends with some class called LinkedListthat has a template parameter (we use a different name (not T) in case the tem-plate argument differs), and that friend class gets to see any member functionsor variables as public.

4.4 Virtual Functions

4.4.1 Constructors and Virtual

Question

I know what virtual function is, but I am still not exactly sure when to use it.For example, in ”shape.h”, why destructor is virtual function but constructoris not?

Answer

Constructors can never be virtual. Every class is responsible to construct itselfand if necessary call the base class constructor. However, it’s a good idea tomake destructors virtual to make sure the derived class destructor gets called.See this post for some more information1.

1http://stackoverflow.com/questions/461203/when-to-use-virtual-destructors

41

Page 44: C++ Coding Guide

Constructors have no need of being virtual because when you construct anobject, you need to know its type explicitly. Consider an example of a hierarchywith a Shape that has a Rectangle child:

Shape * aShape = new Rectangle();

In this situation we know we are constructing a Rectangle and there is noambiguity about what type we have. We explicitly construct the Rectangle andthere is no need at runtime to figure out what we want to construct.

Remember that with inheritance, a Rectangle IS A Shape, so we can have aRectangle pointer that assigns to a Shape pointer. However, let’s look at whathappens when we delete this:

delete aShape;

aShape has type Shape pointer, but if we were to just call Shape’s destructorthat wouldn’t be enough, because aShape actually points to a Rectangle object.So we need virtual destructors here so that dynamic binding can step in and atrun time tell us where we need to go (which is to Rectangle’s destructor). Atruntime C++ will check what kind of Shape aShape points to, and then callthe appropriate function (destructor in this case).

Note that none of this is an issue if we aren’t using pointers to access our objects(references count here too, since behind the scenes they are just pretty pointerdereference/address of operations):

Rectangle aRectangle;

Shape & someRef = aRectangle;

someRef.draw(); // calls Rectangle’s draw,

// not Shape’s (assuming draw was virtual)

In summary the whole point of declaring a function virtual is so that you canhave pointers to related (in class hierarchy terms) types and still end up callingthe correct function.

4.5 Iterators

4.5.1 General Iterator Confusion

Question

Though its pretty late for this assignment I would still like to know this for thefuture assignments. As I was looking through the posts about iterators I wasa little confused about one thing. Iterators seem great for taking internal dataand showing it to the outside world. Suppose we want to get data from theoutside and put it in some objects, what role do iterators play there, especially

42

Page 45: C++ Coding Guide

if its complicated objects where we are iterratorating through users and we wantto change specific parts within the User wall or bag of friends?

Answer

Iterators can be used for the purpose you mention in some data structures.Normally a class will have two types of iterators - a const version and a nonconst version. With the non-const version, you can use it to modify data you areiterating over. This doesn’t solve the question of how you can insert or removeelements yet.

Many classes will implement insertion or removal functions that take an iteratoras a parameter. Remember that the iterator refers to a specific element in yourdata structure, so it can be used to delete a specific element if written properly.In addition, it can be thought of as giving an index for insertion, and normallyinsertion functions that take an iterator will insert immediately before or afterthat item. For some data structures these operations don’t make much sense.Consider a Heap, which enforces a special ordering of the elements since it mustmaintain the heap property, so inserting anywhere wouldn’t be supported.

You can see examples of this on std::vector, check out the following two2 refer-ences3.

For the assignment and wanting to change specific parts of a User wall or some-thing, remember that your iterator should return a reference to the type of itemsit is iterating over. Once you have an item, you can then call functions on itlike you normally would. Consider:

// Iterate over all wall posts for every user and change the

// author to something

UserList users;

for( UserList::Iterator it = users.begin(); it != users.end(); ++it )

{

// the type of *it is User. Let’s pretend we have a function

// getWall() that returns a reference to the wall

for( User::Iterator postIt = it->getWall().begin();

postIt != it->getWall().end(); ++postIt )

{

// the type of *postIt is WallPost

postIt->setAuthor( "Jon Snow" );

}

}

Note that in my example above, I have overloaded the -> operator so that Idon’t have to do the ugly looking (*iterator).functionCall().

2http://en.cppreference.com/w/cpp/container/vector/insert3http://en.cppreference.com/w/cpp/container/vector/erase

43

Page 46: C++ Coding Guide

4.6 Inheritance

4.6.1 Initialization Lists

Question

I’m having trouble with the constructor for a class that inherits another class.What is wrong?

Answer

Use initialization lists! There’s a good reason I’ve been repeating this for awhile at lab, and this is precisely one of the cases where normal assignmentwon’t work in a constructor.

When your constructor runs, by the time it reaches the opening curly brace, ithas already allocated and initialized all of your member variables (plus allocatedspace for itself). Remember that when a class is constructed, it must firstconstruct its parent before constructing itself. So:

struct Derived : public Parent

{

int x;

double y;

};

Derived::Derived()

{

// this will first construct the Parent, then us,

// meaning allocating space for our int x and double y

}

if you want to explicitly invoke a constructor other than the default constructorfor your parent class, you need to use initialization list syntax:

Derived::Derived() : Parent( arguments, go, here ),

x( some_initial_value ),

y( another_initial_value )

{

// everything in the initialization list has been

// allocated and initialized before we reach this line

}

44

Page 47: C++ Coding Guide

4.6.2 Is-a string

Question

I was having trouble with correct syntax while implementing my class thatinherits from std string like in the homework description, so I looked it up andsaw an overwhelming number of posts saying to never inherit from string (andtherefore no help on actually how to do it). If we choose to use has-a insteadwill we be counted off?

Answer

One reason it isn’t done is that it just isn’t the style (not idiomatic C++) anda lot of writing C++ revolves around writing code that fits in with a currentlyaccepted style of ”good code.”

Some good reasons are detailed here covering both the is-a and as-a cases. Thebulk of the problems arise from the fact that essentially none of the standardtypes are designed for polymorphism, so none of the functions are virtual. Whenyou inherit from something, your new type is convertible to the type you in-herited from, meaning it can be passed to functions expecting the base type.Since none of your functions are virtual, you can’t actually override anythingin this situation. Additionally, there are issues with various constructors andcopy constructors that won’t get properly called when converting between thederived and base types.

In idiomatic C++, the most commonly used style is ”has-a.”

The way hash functions are done in the standard library is with template special-ization. This means that there is a class called template <class T> std::hash

that is never defined generically, only in terms of specific types T. Here’s an ex-ample of that:

template <class T>

struct MyHasher; // We’ll never define this in terms of just T

template <> // no unknown template parameters

struct MyHasher<std::string>

// this is called a template specialization where T = std::string

{

MyHasher()

{

// any code needed to set up any state associated with the hash

}

size_t operator()( std::string const & stringToHash ) const

{

45

Page 48: C++ Coding Guide

// some math magic to return a number. note that this number

// is independent of your table size. you’d have to factor

// that part in your data structure and not in this function.

}

};

You would use this like the following:

template <class Key, class Value>

void MyHashTable<Key, Value>::insert(

Key const & k, Value const & v ) // or wrapped in some node

{

// pretend we have some member variable of

// type MyHasher<Key> named _hashFunction;

size_t hash = _hashFunction( k ) % _size; // or something

// whatever

}

4.6.3 Protected Variables

Question

Why can’t I access protected variables in my superclass from a subclass? Do Ineed to include something after I inherit from a parent?

Answer

You are probably not declaring your inheritance to be public. Remember thatclasses default to private, and structs to public. So when you do:

class MyCoolClass

{

protected:

int x;

};

// note the lack of any public/private/protected keyword

class SubClass : MyCoolClass

{

SubClass()

{

x = 3; // ERROR: x is private in this context

}

};

46

Page 49: C++ Coding Guide

You can’t get access to x, even though it is protected in MyCoolClass. This isbecause by default, if you don’t specify an access level for the inheritance of aclass, it will default to private, making everything and anything in the parentclass private to the derived class. You need to declare the inheritance as eitherpublic or protected to get access to protected members.

First things first: you cannot inherit from ArrayList because ArrayList is nota type. Since you templated ArrayList, it must always be accompanied byits template parameter to fully define the type, so you should be inheritingfrom ArrayList<T>. Secondly, when you use templates in inheritance, or havemultiple inheritance, you need to help the compiler figure out what you meanwhen you say you want a variable that comes from a parent class. You can dothis in one of three ways:

Let’s assume our base class is something like:

template <class T> class Parent { protected: int x; };

The first way to access x is to use the this pointer to disambiguate the sourceof x:

template <class T>

class Child: public Parent<T>

{

public:

Child()

{

this->x = 3; // Explicitly state I am using a copy of x

// that can be reached from myself

}

};

The second way is to explicitly say where x is coming from:

template <class T>

class Child: public Parent<T>

{

public:

Child()

{

Parent<T>::x = 3; // I’m changing the x I got from Parent<T>

}

};

The last way is to say that you will be using a parent’s variable (or function) inyour subclass:

template <class T>

class Child: public Parent<T>

{

47

Page 50: C++ Coding Guide

protected:

// I’ll use Parent<T>’s x as a protected variable here

using Parent<T>::x;

public:

Child() { x = 3; }

};

Note that with the using syntax, you can actually change the access level fromprotected back to public (but never from private to something else, because youcan never see private outside of the class it is defined in). All of this extra verbosesyntax only applies if you use multiple inheritance or templated inheritance.

4.7 Const

4.7.1 error: passing ’const Wall’ as ’this’ argument of’WallPost Wall::get(int)’ discards qualifiers

Question

I seem to run into this error quite a bit, and don’t completely understand what’sgoing on. In general ,what is going wrong here?

Answer

You are getting this error because you are trying to access a const method froman instance of a class that is itself not const. Something is considered const ifyou either declare it as such or pass it by const reference to a function. Forexample:

struct myStruct

{

void nonConstFunction();

void someFunction() const;

};

void doSomething( myStruct const & obj )

{

obj.someFunction(); // ok since someFunction is marked as const

obj.nonConstFunction(); // NOT ok, discards const qualifiers

}

In general just remember that const objects can only call const methods.

48