with C++ Templates Templates Thomas Gschwind <thg at zurich dot - - PowerPoint PPT Presentation
with C++ Templates Templates Thomas Gschwind <thg at zurich dot - - PowerPoint PPT Presentation
Advanced Software Engineering with C++ Templates Templates Thomas Gschwind <thg at zurich dot ibm dot com> Templates Polymorphisms Specialization Declaration and Use Classes and Members Ambiguities An Example
Templates
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
83
- Polymorphisms
- Declaration and Use
- Ambiguities
- Specialization
- Classes and Members
- An Example (pvector)
Types of Polymorphisms
- “Ad-hoc”
- Overloading
- Statically resolved by the compiler (using argument types)
- Dynamic
- Using virtual member functions
- Method to be invoked identified during run-time
(using the virtual method table)
- Static or Parametric
- Using templates
- Function to be invoked identified statically
- Concrete Functions/Classes are generated for the individual parameter types
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
84
Templates – Why?
- Writing gcm, lcm, and swap for all kinds of types is tedious
- We want to define the function once and use it for all types
possible
- Sort of like Lisp, Smalltalk, Python, Ruby, you name it…
- Just more efficiently
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
85
Templates: Declaration and Definition
- Allow the use of the same function/class for different types
- The types become new compile-time parameters
- Types need to implement the routines used by the template
- The definition must be available to the compiler
- Are checked and resolved statically (during compile time)
- Function calls can be resolved during compilation time
- Support generic programming
- Many functions are the same independently of the data type
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
86
template<class T> inline T min(T a, T b) { return a<b ? a : b; }
This is “old style”, one should use typename instead but some people still prefer class. If you use an antiquated C++ compiler you may have to use class.
Templates: Use
- Are invoked like any other function (mostly)
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
89
template<typename T> inline T min(T a, T b) { return a<b?a:b; } const double pi=3.141596; void f() { min(2.718282, 1.0); min('a', 'z'); min(1, 26); min(pi, 2.718282); min('a', 26); min(2.718282, 1); }
Templates: Use
- Template parameters must be unambiguously resolved
- Otherwise, the ambiguity needs to be resolved manually
template<typename T> inline T min(T a, T b) { return a<b?a:b; } const double pi=3.141596; void f() { min(2.718282, 1.0); min('a', 'z'); min(1, 26); min(pi, 2.718282); min('a', 26); min(2.718282, 1); } template<typename T> inline T min(T a, T b) { return a<b?a:b; } const double pi=3.141596; void f() { min(2.718282, 1.0); // ok min('a', 'z'); // ok min(1, 26); // ok min(pi, 2.718282); // ok min('a', 26); // error, ambiguous min(2.718282, 1); // error, ambiguous }
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
90
Templates: Resolving Ambiguities
- Unlike for “normal” functions,
there is no implicit conversion for templates
- Explicit
- If necessary
min<int>('a', 26);
- Or even if unnecessary
min<const double>(pi, 2.718282);
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
92
Mixing Templates and Non-Templates
- Templates and non-templates can be mixed
- Can define a template-based function min
- And define a non template-based function min at the same time
- Non-templates are preferred over templates if no type conversion
necessary
template<class T> inline T min(T a, T b) { return a<b ? a : b; } inline double min(double a, double b) { return a<b ? a : b; }
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
93
Templates: Resolving Ambiguities (cont‘d)
- We can create separate helper functions
- Helper functions may be based on the underlying template
- This approach not only looks tedious
but is also error-prone, clumsy, …
inline int min(int x, int y) { return min<int>(x,y); } inline double min(double x, double y) { return min<double>(x,y); }
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
94
min Template – A Problem?
- We have seen it works fine with numbers and characters
- What about C-style strings?
- The above will compare the addresses where the strings are stored
- It will return the string with the smaller address
- This is not what the typical developer wants …
- For strings it would be better to use a lexicographical comparison
such as strcmp …
cout << min("Hello", "World") << endl;
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
95
Specialization (1st Attempt)
- Templates and non-templates can be mixed
- Define a non template-based function min for C strings
template<typename T> inline T min(T a, T b) { return a<b ? a : b; } inline char *min(char *a, char *b) { return strcmp(a,b)<0 ? a : b; } inline const char *min(const char *a, const char *b) { return strcmp(a,b)<0 ? a : b; } #include "min.h" void foo(char *x, char *y, const char *z) { cout << min(x,y) << endl; cout << min(x,z) << endl; cout << min<const char*>(x,z) << endl; } // yes // yes // compiles but wrong We are asking for the template, so we get the template …
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
96
Template Specialization (2nd and Final Attempt)
- C++ allows us to specialize an existing template for specific types
template<typename T> inline T min(T a, T b) { return a<b ? a : b; } template<> inline char *min<char *>(char *a, char *b) { return strcmp(a,b)<0 ? a : b; } template<> inline char *min<const char *>(const char *a, const char *b) { return strcmp(a,b)<0 ? a : b; } #include "min.h" void foo(char *x, char *y, const char *z) { cout << min(x,y) << endl; // yes cout << min(x,z) << endl; // error cout << min<const char*>(x,z) << endl; // yes }
Compiler error; as we discussed, there is no implicit parameter conversions for templates.
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
97
Templates: Classes and Members
- Works exactly the same
- Simply put
template <typename T, typename U, …>
in front of the declaration
- It is even OK, to introduce new template parameters for individual
member functions
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
98
A Persistent pvector Class
- We want to implement a persistent version of C++’s vector class
- Reads all elements from a file in the constructor
- Writes all elements back to the file in the destructor
template<typename T> class pvector { string filename; vector<T> v; … public: pvector(string fname) : filename(fname) { readvector(); } ~pvector() { writevector(); } void push_back(const T &el) { v.push_back(el); } void pop_back() { v.pop_back(); } …
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
99
A Persistent pvector Class (cont’d)
template<typename T> class pvector { string filename; vector<T> v; void readvector() { ifstream ifs(filename); for(;;) { T x; ifs >> x; if(!ifs.good()) break; v.push_back(x); } } void writevector() {
- fstream ofs(filename);
typename vector<T>::iterator fst=v.begin(), lst=v.end(); while(fst!=lst) ofs << *fst++ << endl; } …
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
100
OR starting with C++11, simply: for (const T &elem : v) ofs << elem << endl;
A Persistent pvector Class (cont’d)
- What happens if we pass the pvector around?
- Hence, maybe we want to disable the copy-constructor for
pvector<T>
void foo(pvector<int> pv) { if(pv.size()>0) cout << pv[0] << endl; pv.push_back(17); } int main(int argc, char *argv[]) { pvector<int> pv("/tmp/pvector-int.txt"); foo(pv); }
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
101
Advanced Software Engineering with C++ Templates
Separate Compilation
Thomas Gschwind <thgatzurichdotibmdotcom>
Separate Compilation
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
103
- C++ versus Java
- Variables
- Routines (Functions & Operators)
- Types (Structures, Classes)
- Makefiles
Separate Compilation
- Why?
- Having only one source file is unrealistic
- Break the code up into its logical structure
- Reduction of compile time
- Only changed parts need to be recompiled
- How?
- Use multiple source files
- Need to know what information about functions and variables
“used” from other files
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
104
Separate Compilation in Java
- Each Java file is compiled into a class file
- If a Java class invokes a method of another class,
the compiler consults that other class file to
- Determine whether the class provides the requested method
- Determine whether a class file implements a given interface
- etc.
- Hence, the .class file contains the entire interface
- That’s why in Java, the compiler needs the class path
- Finally, all class files are loaded by the Java Virtual Machine
(and “linked”)
- Java source code can be reconstructed from .class file
(see Java Decompiler: jd, jad)
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
105
Some Java Trivia
- Let us write Hello World in Java
- In order to simplify the reconfiguration (e.g., translation)
- Put all the constants into one Java file
- Put the complex application code into another
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
106
public class Const { public static final String msg="Hello World!"; } public class Cool { public static void main(String[] args) { System.out.println(Const.msg); } }
Some Java Trivia
- Now compile both Java files and run Cool
- Change the msg in Const.java, recompile Const.java, and run Cool
public class Const { public static final String msg="Hello World!"; } public class Cool { public static void main(String[] args) { System.out.println(Const.msg); } }
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
107
Some Java Trivia
- Now compile both Java files and run Cool
- Change the msg in Const.java, recompile Const.java, and run Cool
- Cool still prints the old message! Why?
- javac inlines constants
- And according to the Java specification that’s legal
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
108
public class Const { public static final String msg="Hello World!"; } public class Cool { public static void main(String[] args) { System.out.println(Const.msg); } }
Separate Compilation in C++
- By convention, each source file is compiled into an object file
- Object files provide minimum necessary to execute the code
- Object files do not provide enough information for the compiler to
identify
- Functions provided by another compilation unit
- Layout of a user-defined type
- Object files are not used during the compilation
- C++ and C use “header” files to store the interfaces of the object file
- These files need to be supplied by the developer
- In C/C++, we have the include path instead
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
109
Header Files
- C++ uses so-called header files for separate compilation
- The header file can be viewed as the object file’s interface
- Hence, header files are another encapsulation mechanism
- They describe the interface(s) provided by the object files
- Describe everything that should be exported to other compilation units
- What goes into the header file?
- “Everything” that should be exported (i.e., used in other files)
- “Nothing” that causes the compiler to immediately generate code
- Except, for a small number of exceptions
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
110
Header Files: Prevent Multiple Inclusion (Guard)
- Header files need protection from being included multiple times
- Otherwise, this may cause compile errors
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
111
#include "vars.h" int my_dumb_global_variable=17; const double e=2.718281; #ifndef VARS_H_ #define VARS_H_ extern int my_dumb_global_variable; extern const double e; const double pi=3.141596; #endif
if VARS_H_ is not defined process the following lines define _VARS_H end the last open #if… section Process the file “vars.h” This pattern ensures that header files won’t be included multiple times
vars.h vars.cc
Header Files: Prevents Multiple Inclusions (#pragma)
- An alternative to the guard statement is to use #pragma once
in your header files
- Although a non-standard, it is widely supported
- Serves the same effect, less code to write
- Can be more efficient during compilation, file needs to be opened only once
- No name-clashes
- Compiler may not always be able to identify whether two files are equal
(hard links, symbolic links, multiple filesystem mounts, etc.)
- My suggestion
- Stick to the standard
- Use a system to uniquely generate the guard name
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
112
Header Files: Variables
- Variables
- Declaration goes into the header
(if variable is to be accessed elsewhere)
- Definition goes into the implementation file (allocates memory)
- Constant Variables
- Declaration goes into the header
(if variable is to be accessed elsewhere)
- Definition goes either into the header or the implementation file
- If in the header, definition may be allocated multiple times
- No problems for individual values (constant and small)
- Be careful with large constants such as large constant arrays
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
113
Header Files: Variables Example
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
114
#include "vars.h" int my_dumb_global_variable=17; const int primes[]={2, 3, 5,…, 1234567891}; #ifndef VARS_H_ #define VARS_H_ extern int my_dumb_global_variable; const double pi=3.141596; extern const int[] primes; #endif
Use extern to declare a variable to be defined elsewhere. No memory will be allocated for the variable
vars.h vars.cc
Constants may be defined in the header or declared like other variables. Include the header (for consistency checking) Variables are defined in the implementation file. Do not repeat constants defined in the header.
Header Files: Functions
- Functions
- The declaration of the function goes into the header
- Definition goes into the implementation file
- Inline Functions
- If they are to be inlined in the corresponding implementation file only,
treat them like functions
- If they are to be inlined globally (typical),
declaration and definition go into the header file
- Necessar for the compiler to know
the implementation to be use instead of the function call
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
115
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
116
Header Files: Functions Example
#include "util.h"
int gcf(int a, int b) {
if (a<b) swap(a,b); while (b!=0) { a=a-b; if (a<b) swap(a,b); } return a; }
#ifndef UTIL_H_ #define UTIL_H_
inline void swap(int &a, int &b) {
int c=a; a=b; b=c; }
extern int gcf(int a, int b); inline int lcm(int a, int b) {
return (a/gcf(a,b))*b; }
#endif
util.h util.cc
Inline functions are declared and defined in the header file. Use extern to declare a function. Extern for function declarations is
- ptional. If there is no function
body, it cannot be a definition. Functions are defined in the implementation file. Do not repeat inline functions defined in the header.
Header Files: Types (typedef, struct, class)
- The type declaration and definition go into the header
- The layout of the type needs to be known to the compiler
in all compilation units that need to allocate the type
- For members of a type the same rules as for functions apply
- Member declarations into the header
- Member definitions (except for the ones to be inlined) into the
implementation file
- All members of a class even if they should not be visible outside the
compilation unit need to be declared as part of the type definition
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
117
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
118
Header Files: class Example
class fraction { int c; int d; public: fraction(int cntr=0, int denom=1) : c(cntr), d(denom) { /*void*/ } fraction operator*(const fraction &b); fraction operator/(fraction b) { swap(b.c, b.d); return (*this)*b; } }; #include "fraction.h" #include "util.h" fraction::fraction operator*(const fraction &b) { fraction r; int f1=gcf(this->c,b.d), f2=gcf(b.c,this->d); r.c=(this->c/f1)*(b.c/f2); r.d=(this->d/f2)*(b.d/f1); return r; }
- perator/ is an implicitly inline
member, inline functions go into the header file.
fraction.h fraction.cc
The complete layout of a type (public, protected, private members) go into the header file.
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
119
Header Files: Templates
- Type declaration and definition go into the header
- The code for a template is generated when it is parameterized
- Hence, the compiler needs the full code to instantiate the template
- If the template is only to be parameterized with a small set of types
- Can treat template functions and template classes like normal functions and
classes
- Need to instantiate the class in an implementation file that has access to the
full template definitions (for instance, template class pvector<string>;)
main program
- Include header files
- System header files first allows to
find compile errors in header more easily
- Own header files first allows to
find missing definitions in header more easily
- Compile each file
- Put dependencies into
Makefile
- If implementation file changes, it
needs to be recompiled
- If a header file changes, layouts
- f types may have changed, all
files including it need to be recompiled
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
120
#include <stdlib.h> #include <iostream> #include "fraction.h" #include "util.h" #include "vars.h" void main(int argc, char *argv[]) { int arg=atoi(argv[1]); cout << arg << "^2*pi=“ << arg*arg*pi << endl; cout << "e=" << e << endl; cout << gcf(atoi(argv[1]), atoi(argv[2])) << endl; cout << lcm(atoi(argv[1]), atoi(argv[2])) << endl; … // use of fraction data type }
Makefile
all: main main: main.o fraction.o util.o vars.o g++ -o main main.o fraction.o util.o vars.o main.o: main.cc fraction.h util.h vars.h g++ -c main.cc fraction.o: fraction.cc fraction.h util.h g++ -c fraction.cc util.o: util.cc util.h g++ -c util.cc vars.o: vars.cc vars.h g++ -c vars.cc
Makefile
main.cc fraction.cc fraction.h util.h vars.h main.o fraction.o util.o vars.o main util.cc vars.cc Link the final executable (could also use ldd but tedious)
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
121
Useful Build Tools
- gcc: not only compiles and links files
- Also analyzes sources and generates dependencies between them
(Checkout the -M… options)
- Also allows to analyze java files for the dependencies between them
- ld: link object files
- For C++ files, you need to include the C++ library as well (stdc++)
- nm: list symbols in an object file or program
- Defined, undefined, which section, etc.
- ldd, otool –L: list libraries needed by an object file
- Simply list the shared libraries the object file is dependent on
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
122
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
123
Makefiles (made easy)
- As mentioned before, gcc allows to generate source dependencies
- This Makefile can be used as a generic starter for your Makefile
CXXFLAGS=... CXXFLAGS+=-Wall -Wextra -Werror OBJS=main.o # "main" file OBJS+=fraction.o ... # others… all: main clean: rm -f main *.o distclean: clean rm -f .depend/*.d rm -f *~ ...
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
124
Makefiles (cont’d)
...
- include $(addprefix .depend/,$(OBJS:.o=.d))
%.o: %.cc g++ $(CXXFLAGS) -c -o $@ $*.cc @g++ -MM $(CXXFLAGS) -c $*.cc >.depend/$*.d main: $(OBJS) g++ $(LDFLAGS) -o $@ $(OBJS)
Advanced Software Engineering with C++ Templates
Memory Management
Thomas Gschwind <thgatzurichdotibmdotcom>
Memory Management
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
126
- Allocation & Deallocation
- Stack
- Variables (Pointers, Arrays,
References)
- Heap
- Memory and Classes
Memory Management: Java vs. C++
- In Java
- Built-in types are stored on the stack
- User-defined types are always stored on the heap
(only their references are stored on the stack)
- Memory is automatically freed by the garbage collector
- In C++
- Built-in types and objects can be stored on the stack and heap
- C++ supports references and pointers; both can refer/point to objects and
built-in types both on the stack and the heap
- Stack memory is managed automatically
- Heap memory needs to be allocated and freed explicitly
(with delete and delete[])
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
127
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
128
Functions: Parameter Passing
- Call by Value
- Argument to be passed copied from the caller’s scope into the callee’s scope
- Callee operates on its own copy
- Call by Reference/Pointer
- Callee receives a references to the argument passed by the caller
- Callee operates on caller’s copy
- C++/C
- All parameters can be passed by value, by pointer, or in C++ also by reference
- Java
- Primitive types are passed by value
- Class instances are passed by reference
- C#
- Value types (primitives and structs) by value or reference
- Class instances by reference
Memory Organization
- The memory needs to store
different things
- The program
- Global variables
- Local variables
(on the stack)
- Etc.
Memory
0xffff:
. . . . . . Stack Program . . Global Variables . . .
0x0000:
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
129
The Stack
- Stores local variables, return addresses, etc.
- Right side shows the (simplified) stack after
fact(2) has been invoked
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
130
int fact(int n) { int m=n-1; if (n<=2) return n; else return n*fact(m); } int main() { cout << "fact(4)=" << fact(4) << endl; return 0; }
0x0ff8: 0x0ffc: n==4, m==3 “line 8” . . main() - code . . 0x0ff0: “line 4” n==3, m==2 “line 4” n==2, m==1 0x0fec: 0x0fe8: 0x0fe4: 0x1000: result result result 0x0fdc: 0x0fd8: 0x0fd0: fact(4) fact(3) fact(2)
1 2 3 4 5 6 7 8 9 10
Pointers (TYPE*)
- Pointers are a fundamental concept of C++
- Pointers should be used sparsely and carefully
- A pointer points to a value or object stored anywhere in memory
- Since pointers can point to different TYPEs of values, there are
different types of pointers denoted by TYPE*
- A pointer is similar to an iterator iterating over a collection of
elements of type TYPE
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
131
Pointer Operations
- &-Operator
- Obtain the address where a value is stored in memory (lvalue)
- Returns a pointer to the type of the lvalue
- &(lvalue)
- *-Operator
- Dereference a pointer (i.e., manipulate the memory that the pointer points
to)
- Return the type of the value the pointer points to
- *(expr)
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
132
An lvalue (left value) is an expression that can occur on the left or the right hand side
- f an assignment expression. An rvalue is
an expression that can only occur on the right hand side.
Pointers (cont’d)
- Pointers store an address
- This is visible to the developer
- The type of the pointer indicates the
type of object stored at the address
Address-Space
0x0ff8: 0x0ffc: b==5 a==3 . . . . main() . . . . . . . 0x0ff4: pa==? a==9
int main() { int a=3; int b=5; int *pa; pa=&b; pa=&a; // ok *pa=9; // ok }
pa==0x0ff8 pa==0x0ffc 0x1000:
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
133
Modifying Arguments with Pointers
- Implement a routine that exchanges the value of two arguments
- Arguments must be an lvalue
- Invoked with c_swap(&var1, &var2);
- In Java, this is impossible, arguments need to be wrapped within
an object
void c_swap(int *x, int *y) { int z=*x; *x=*y; *y=z; } int a=3, b=5; c_swap(&a, &b);
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
134
Arrays
- Arrays provide memory for several values of the same type
- In C++ an array is typically equivalent to a pointer of the first
element of the array
0x0ff8: 0x0ffc: buf[2]==? buf[3]==? . . main() . . 0x0ff4: buf[1]==? buf[0]==? bufp==0x0fec 0x0ff0: 0x0fec: 0x0fe8: 0x1000:
void foo() { int buf[4]; int *bufp=buf; // ok buf[0]=3; // ok *buf=3; // same *bufp=*buf; // same ++bufp; *bufp=*buf+1; }
buf[0]==3 “line 8” bufp==0x0ff0 buf[1]==4
buf
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
135
Arrays (Be Careful)
- Arrays are not range checked (buffer-overflow)
- C++ happily assigns a value to buf[4]
- Developer has to keep track of this
- There are several libraries providing a safe version of arrays (we’ll
come back to this)
- Arrays “cannot” be returned as the result of a function
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
136
References (TYPE&)
- Similar to pointers – sometimes more elegant
- However, like pointers they should be used
- Sparsely and
- Carefully
- A reference refers to a value or object stored in memory, it is
another name (alias) for a given value or object stored in memory
- Unlike a pointer, it cannot be changed to refer to a different
location
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
137
References (cont’d)
- References are similar to pointers
- Except their implementation is
“invisible”
Address-Space
0x6f04: 0x6f08: b==5 a==3 . . . . main() . . . . . . . 0x6f00:
int main() { int a=3 int b=5; int &pa=a; pa=7; // ok //&pa=b; // error pa=b; // ok }
pa/a==3 pa/a==7 pa/a==5
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
138
Modifying an Argument with References
- Implement a routine that exchanges the value of two arguments
- Argument must be an lvalue
- Invoked with swap(var1,var2);
- Similar to VAR parameter in Pascal
- In Java, this is impossible, in C#, however, it is possible
void swap(int &a, int &b) { int c=a; a=b; b=c; }
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
139
Returning References
- Functions may also return references
- Allows the function to be used as lvalue
class fraction { public: … // conversion fraction to double
- perator double() { return (double)c/d; }
// references as return value int &counter() { return c; } int &denominator() { return d; } }; void normalize(fraction &a) { int f = gcf(a.counter(), a.denominator()); a.counter() = a.counter() / f; a.denominator() /= f; }
counter() and denominator() may now be used on the left side of operator= (i.e., as lvalue) However, this may make it harder to change the internal representation of fraction numbers in the future
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
140
Use References with Care
- References have the same characteristics as a pointer
- The variable a reference point should exist as long as the reference itself
- Never return a reference to a local variable!
- BTW, avoid static local variables (they are like global variables)
- References are taken implicitly
- Makes their code nicer to read
- BUT easy to overlook that they pose the same risks as pointers
- May be generated “by accident”
- The Google Coding Style suggests to avoid references as return
values and instead stick to pointers
- Advantage, have to write return &variable;
- Disadvantage, may not always be intuitive
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
141
Commonly Used (Safe) Patterns
- Function references elements passed to it by caller
- swap(int &a, int &b)
- If a and b are valid in caller scope, they “must be” valid in callee’s scope
- Caveat: make sure that it is obvious to the caller that a and b are modified
- print_vector(const vector<string> &v)
- Almost as above, except with const we promise that v will not be modified
- Avoids duplication of v which is otherwise passed by value (very common
pattern)
- Function returns reference to element passed to it
- ostream &operator<<(ostream &os, const T &element)
- Same argument as above we return to caller a reference to an object that is his
- Member function returns reference to element of its class
- int &fraction::counter()
T &vector::operator[](size_t index)
- Actually, same as above, since this is implicitly passed to the member function
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
142
The Heap
- As soon as data is allocated on the heap, think
about its ownership and life-cycle, whoever allocates it, should deallocate it
- Stores non-local and non-global variables
- If memory needs to be allocated
during runtime (e.g., linked lists, large arrays, …)
- Memory needs to be explicitly allocated
(like new like in Java)
- Useful for returning large user-defined types or
arrays (although consider a vector) from routines
Memory
0xffff:
. . . . . . Stack Program . Global Variables . Heap . .
0x0000:
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
143
Memory Allocation & Deallocation
- “No” garbage collection in C/C++
- Memory needs to be allocated explicitly (new, new[]) and freed (delete,
delete[])
- Some lazy and/or old-fashioned C programmers still use malloc when they
want uninitialized memory (there can be an advantage to this: realloc)
- The proper way of allocating uninitialized memory, however, is
::operator new (size_t bytes)
- Error handling
- bad_alloc will be thrown (new …),
- or NULL is returned (new (nothrow) …)
- Initialization
“Unfortunately, overuse of new (and of pointers and references) seems to be an increasing problem.” Bjarne Stroustrup
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
144
Returning an Array
- An array per se is a pointer
- Cannot return the entire array
- Can only return the pointer
Memory
0xffff:
. . . .
0x0000:
int* foo2(int n) { int *buf=new int[n]; for(int i=0; i<n; ++i) { buf[i]=i*i; } return buf; } void main() { int *buf=foo2(10); for(int i=0; i<10; ++i) { cout << buf[i] << endl; } delete[] buf; }
buf[2]==4 buf[3]==9 buf[1]==1 buf[0]==0
Program .
0x1000: 0x2000: buf==? “line 7” buf==0x2000 0x0ff8: 0x0ff4: 0x0ff0: 0x2004: 0x2008: 0x200c: result
buf
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
145
Returning an Array (Style!)
- The example has a style problem
- Can you spot it?
- Memory is allocated in foo2 and deallocated in main
- If possible one should deallocate memory in the same method as it is
allocated
- What are the alternatives?
- Allocate the array in main and pass it into foo2
- If that is not possible implement a foo2_cleanup routine that deallocates the
memory returned by foo2
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
146
Returning an Array with Nice Memory Management
- Frequently, in C, when a function f allocates memory for us,
there is a sister function f’ that releases the memory allocated by f
(e.g., getaddrinfo, freeaddrinfo)
int* new_foo2(int n) {
int *buf=new int[n]; for(int i=0; i<n; ++i) { buf[i]=i*i; } return buf; }
void delete_foo2(int *buf) {
delete[] buf; } void main() {
int *buf=new_foo2(10);
for(int i=0; i<10; ++i) { cout << buf[i] << endl; }
delete_foo2(buf); }
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
147
C++ auto_ptr<T> (deprecated since C++11)
- C++ does not perform garbage collection but
has wrappers that come close
- auto_ptr<T>
Delete object pointed to when ptr is destructed
void foo() { auto_ptr<int> pi (new int); *pi=17; cout << "*" << pi.get() << "=" << *pi << endl; auto_ptr<int> pj(pi); // transfer ownership; pi points to NULL *pj=19; cout << "*" << pi.get() << endl; // displays 0 cout << "*" << pj.get() << "=" << *pj << endl; } // deallocate the integer
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
148
Works the same with C++11 unique_ptr except use pj(pi.release()) to transfer ownership
C++11: Memory Management
- More versatile pointer wrappers
- auto_ptr<T> depending on the context should be replaced with
unique_ptr<T> or auto_ptr<T>
- The following “pointers” provided by the Standard Library help
- unique_ptr<T>
Delete object pointed to when unique_ptr is destructed
- shared_ptr<T>
Delete object pointed to by shared_ptr when this is the “last” pointer pointing to the object – works similar to the smart_ptr exercise
- weak_ptr<T>
Useful in combination with shared_ptr if cyclic structures are used, allows to break up cycles, need to be converted to shared_ptr before object may be accessed, see documentation for details
- Don’t use them as all-round solution
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
149
C++11: Returning an Array
- unique_ptr and shared_ptr can be used to return objects
- The pointers will destruct the object when “they” no longer point
to it
unique_ptr<int[]> make_foo3(int n) { unique_ptr p{new int[n]}; for(int i=0; i<n; ++i) { p[i]=i*i; } return p; } void main() { auto buf = make_foo3(10); for(int i=0; i<10; ++i) { cout << buf[i] << endl; } }
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
150
Frequently, a C++ vector will Work Best?
- The previous code is great if you insist on C-style memory management
- Why not simply return a vector<int>
- No worries about memory management
- More readable, more C++-like
vector<int> foo2(int n) {
vector<int> res(n); for(int i=0; i<n; ++i) { res[i] = i*i; } return res; } void main() {
vector<int> buf=foo2(10);
for(int i=0; i<10; ++i) { cout << buf[i] << endl;
} }
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
151
Creates n default ints; if that is a problem, use: vector<int> res; res.reserve(n); for(int i=0; i<n; ++i) { res.emplace_back(i*i); } return res;
152
Summary
- Templates
- Definition
- Use
- Specialization
- Separate Compilation
- Memory Organization & Management
- Allocation & Deallocation
- Stack
- Variables (Pointers, Arrays, References)
- Heap
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
Exercise L2.1 [former L1.5]: pvector<T>
- Implement the persistent vector data type.
- Experiment with the persistent vector and use it in combination
with different data types. What do you observe? Why do you
- bserve that behavior? How can it be changed?
- What happens if we pass the pvector<T> around?
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
153
Exercise L2.2
- Upgrade your RPN calculator to add the following new
functionality:
- It should be template enabled RPN<T>() will create your RPN calculator to
work with type T. Ensure it works with your fraction user defined type.
- Make use of our pvector class to store the numbers the user pushes onto
the stack persistently.
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
154
Exercise L2.3: Under the Hood
- Have a look at the following swap routines
- void swap(int &a, int &b) { int c=a; a=b; b=c; }
- void c_swap(int *a, int *b) { int c=*a; *a=*b; *b=c; }
- Let the compiler compile the code but ask the compiler to stop at
the assembly stage $ gcc -S -o source.s source.cc
- Compare the assembly code, what do you observe?
- How do you interpret the difference(s)?
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
155
Exercise L2.4: Inline
- Revisit your program that shows a function that cannot be inlined
- Put the function to be inlined into a separate compilation unit
- Once into the cc file as a normal function
- Once into the header
- Again, generate the assembly code for your program, what do you
- bserver
- Useful compiler options
- -O0 asks the compiler to not optimize the output
(compiler doe not inline functions by itself)
- -O3 asks the compiler to apply almost all optimizations
(compiler may even inline functions not marked as such)
- -S asks the compiler to generate assembly code only
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
156
Exercise L2.5: Emulating pointers
- Implement a dumb_pointer. Use the operator* and operator->
- perators to implement a class that simulates a pointer.
- Implement another class that works like the dumb_pointer but is
called smart_pointer and uses reference counting.
- No, using any of the C++ auto_ptr, unique_ptr, shared_ptr,
weak_ptr classes is not an implementation option
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
158
Exercise L2.5 (cont’d)
void print(smart_pointer<Object> p) { cout << p.counter() << ": " << *p << endl; } void foo() { Object *o1=new Object(1); // let's create our 1st object Object *o2=new Object(2); // let's create our 2nd object smart_pointer<Object> p(o1); // ref counter is 1 for 1st object cout << p.counter() << endl; // displays 1 smart_pointer<Object> q(p); // another smart pointer that points to o1 (overload copy constructor) cout << p.counter() << endl; // displays 2 (two smart pointers refer to o1) cout << q.counter() << endl; // displays 2 (two smart pointers refer to o1) smart_pointer<Object> r(o2); // ref counter is 1 for 2nd object cout << r.counter() << endl; // displays 1 q=r; // decrease counter for 1st object and // increase counter for 2nd object (overload assignment operator) cout << p.counter() << endl; // displays 1 cout << q.counter() << endl; // displays 2 cout << r.counter() << endl; // displays 2 print(p); // displays 2, and the 1st object, don't delete the object pointed to by p print(q); // displays 3, and the 2nd object, don't delete the object pointed to by q print(r); // displays 3, and the 2nd object, don't delete the object pointed to by r cout << *p << *r << endl; // display 1st and 2nd object (overload operator*) cout << p->method1() << q->method2() << r->method3() << endl; // invoke method1 on 1st object and // invoke method2 on 2nd object and // invoke method3 on 2nd object (overload operator->) } // now the destructors of p, q, and r are called, make sure that 1st // and 2nd object is each deleted once (i.e., when the counter reaches 0)
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
159
Next Lecture
- Standard Library II: Algorithms
- Templates III: Implementation
- Inheritance
Have a nice weekend, see you next week
- Th. Gschwind. Advanced Software Engineering with C++ Templates.
160