Post on 26-Dec-2015
Introduction to Effective C++ Introduction to Effective C++ ProgrammingProgramming
Kwang Hee Ko
December 6, 2010
Modeling and Simulation LaboratorySchool of Mechatronics
Gwangju Institute of Science and Technology
IntroductionIntroduction
Don’t have be a “C++ language lawyer” to design a good class. But there do exist rules to follow!!!
ReferencesReferences T. Cargill, C++ Programming Style, Addison-Wesley, New York, 1992. T. Cargill, Exception Handling: A False Sense of Security. C++ Report,
6(9):21-24, 1994. J. Coplien, Advanced C++: Programming Styles and Idions, Addison-
Wesley, New York, 1992. M. A. Ellis and B. Stroustrup, The Annotated C++ Reference Manual,
Addison-Wesley, New York, 1990. S. Meyers, More Effective C++: 35 New Ways to Improve Your Programs
and Designs, Addison-Wesley, New York, 1996. S. Meyers, Effective C++: 50 Specific Ways to Improve Your Programs and
Designs, Addison-Wesley, New York, 2nd Edition, 1998. R. Murray, C++ Strategies and Tactics, Addison-Wesley, New York, 1993. B. Stroustrup, The C++ Programming Language, Addison-Wesley, New
York, 3rd Edition, 1997.
TermsTerms
Declaration vs. definitionDefault constructor(default) copy constructorInitialization vs. assignmentInheritance, polymorphism, OOP, etc.
General GuidelinesGeneral Guidelines
Develop your code under at least two compilers. Improve your understanding of C++. Familiarize yourself with the standard library
(STL). Prefer compile-time and link-time errors to runtime
errors. Pay attention to compiler warnings. Ensure that non-local static objects are initialized
before they are used. Know what functions C++ silently writes and calls.
Minimize “C habits” in your C++ Minimize “C habits” in your C++ ProgramProgram
Prefer const and inline to #define.
Prefer iostream to stdio.h.
Prefer new/delete to malloc/free.
Prefer C++ style comments.
Take Good Care of Memory Take Good Care of Memory ManagementManagement
Use the same form in corresponding uses of new and delete.
Use delete on pointer members in destructors. Be prepared for out-of-memory conditions. Adhere to convention when overloading operator
new and operator delete. Avoid hiding the normal form of new. Overload operator delete if you overload operator
new.
Understanding Understanding The Standard Template LibraryThe Standard Template Library
Containers– A container is an object that holds other objects. You can
add objects to a container and remove objects from it. Standard Library Algorithms Iterators and Allocators
– Iterators : They provide an abstract view of data. They are an abstraction of the notion of a pointer to an element of a sequence.
The element currently pointed to : *, -> operations Point to next element : ++ Equality : ==
– Allocators : They are used to insulate container implementations from details of access to memory.
ContainersContainers
<vector> : one-dimensional array <list> : doubly-linked list <deque> : double-ended queue <queue> : queue
– queue, priority queue <stack> : stack <map> : associative array
– Map, multimap <set> : set
– Set, multiset <bitset> : set of booleans
Example : Vector 1 (Use of []) Example : Vector 1 (Use of []) #include <iostream>#include <vector>#include <algorithm>
using namespace std;
int main(void){ vector<int> v(5); int i; for(i=0;i<5;i++) { v[i] = i; cout << v[i] << endl; } int sum=0; for(i=0;i<5;i++) sum += v[i]; cout << "Sum : " << sum << endl;}
Example : Vector 2 Example : Vector 2 (Use of Iterators) (Use of Iterators)
#include <iostream>#include <vector>#include <algorithm>using namespace std;int main(void){ vector<int> v(5); int sum; vector<int>::iterator first = v.begin(); vector<int>::iterator last = v.end(); sum = 0; while(first != last) { sum += *first; cout << *first << endl; ++first; } cout << "sum : " << sum << endl;}
Example : Vector 3 Example : Vector 3 (Stack Operation) (Stack Operation)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main(void)
{
vector<int> v(5); int sum;
v.push_back(100);
vector<int>::iterator first = v.begin();
vector<int>::iterator last = v.end();
sum = 0;
while(first != last) {
sum += *first;
cout << *first << endl; ++first;
}
cout << "sum : " << sum << endl;
}
Example : Vector 3 Example : Vector 3 (Various Operations) (Various Operations)
vector<int> v(5);
v.push_back(100); // increase the size of v by one
v.pop_back(); // decrease the size by one
v.back() // extract the last object.
v.erase(iterator);
v.insert(iterator,int); // insert int after what iterator points to
v.size() // current size
v.capacity() //
v.max_size() //
Example : List Example : List (Use of Iterators) (Use of Iterators)
No subscripting : operator[]Bidirectional iteratorAdditional operations
– splice, merge, front, push_front, pop_front– sort, unique
Example : List Example : List (Use of Iterators) (Use of Iterators)
#include <iostream>#include <list>#include <algorithm>using namespace std;int main(void){ list<int> v(5); int sum; list<int>::iterator first = v.begin(); list<int>::iterator last = v.end(); sum = 0; while(first != last) { sum += *first; cout << *first << endl; ++first; } cout << "sum : " << sum << endl;}
#include <iostream>#include <list>using namespace std;typedef list<int> List;typedef list<List> ListofList;void print_list(const List& list1, int id);int main(void){ ListofList listoflist1; for(int i=0;i<3;i++) { List list1; for(int j=0;j<4;j++) list1.push_back(i*4 + j); listoflist1.push_back(list1); } ListofList::iterator it = listoflist1.begin(); ListofList::iterator ie = listoflist1.end(); for(int j=1;it != ie; ++it,++j) { const List &list1 = *it; print_list(list1,j); } return 0;}
Example : List of List 1 Example : List of List 1
Example : List of List 2 Example : List of List 2
void print_list(const List& list1, int id){ cout << "list " << id << " : "; List::const_iterator ils = list1.begin(); List::const_iterator ile = list1.end(); while(ils != ile) cout << *ils++ << ' '; cout << endl;}
Example : Stack Example : Stack
#include <iostream>#include <string>#include <stack>using namespace std;int main(void){ stack<string> s; s.push("banana"); s.push("apple"); cout << s.top() << " " << s.size() << endl; s.pop(); cout << s.top() << " " << s.size() << endl; s.pop(); return 0;}
Example : Queue & Example : Queue & Priority_QueuePriority_Queue
#include <iostream>#include <string>#include <queue>using namespace std;int main(void){ queue<string> s; s.push("banana"); s.push("apple"); cout << s.front() << " " << s.size() << endl; s.pop(); cout << s.front() << " " << s.size() << endl; s.pop(); priority_queue<int> k; k.push(56); k.push(2); k.push(100); k.push(5); cout << k.top() << endl; k.pop(); cout << k.top() << endl; return 0;}
Associative ContainersAssociative Containers
map : a single value is associated with each unique key.
multimap : an associative array that allows duplicate elements for a given key.
set, multiset : degenerate associative arrays in which no value is associated with a key.
mapmap
A sequence of (key, value) pairs that provides for fast retrieval based on the key.
Each key in a map is unique.It provides bidirectional iteratorshash_map : useful for a case that there is no
need to keep the container sorted.
Example : map Example : map #include <iostream>#include <string>#include <map>using namespace std;int main(void){ map<string,int> salary; salary["Steve"] = 2000; salary["Tom"] = 3000; salary["John"] = 4500; salary["Neal"] = 3000; salary["Jane"] = 4000; int total = 0; typedef map<string,int>::const_iterator CI; for(CI p = salary.begin(); p!=salary.end(); ++p) { total += p->second; cout << p->first << '\t' << p->second << '\n'; } cout << "-----------------\n total \t" << total << endl; cout << "Tom's salary : " << salary["Tom"] << endl; cout << "Jane's salary : " << salary.find("Jane")->second << endl; return 0;}
AlgorithmsAlgorithms
STL provides algorithms to serve the most common and basic needs.
Most algorithms operate on sequences using iterators and values.
Function objects – Useful when we want the algorithms to execute
code that we supply.
AlgorithmsAlgorithms
Example for function objectsbool less_than_7(int v)
{ return v < 7; }
Void f(list<int>& c)
{
list<int>::const_iterator p = find_if(c.begin(),c.end(),less_than_7);
}
General GuidelinesGeneral Guidelines
Avoid returning “handles” to internal data.Avoid member functions that return non-
const pointers or references to members less accessible than themselves.
Never return a reference to a local object or to a dereferenced pointer initialized by new with in the function (memory leakage).
Postpone variable definitions as long as possible.
General GuidelinesGeneral Guidelines
Public inheritance models : “isa” relation?– Ex. A bus is a vehicle?
Never redefine an inherited non-virtual function.
Never redefine an inherited default parameter value.
Avoid cast down the inheritance hierarchy.Model “has-a” or “is-implemented-in-
terms-of” through layering.
Ambiguities under Multiple Ambiguities under Multiple InheritanceInheritance
Case 1
Base 1
f(); g();
Base 2
f(); h();
Derived
j(); k();
Derived d;
d.g(); // OK
d.h(); // OK
d.f(); // Ambiguous!!!
d.Base1::f(); // OK
d.Base2::f(); // OK
Ambiguities under Multiple Ambiguities under Multiple InheritanceInheritance
Case 2
Left
int y;
Right
int z;
Bottom
int a
Top
int x;
Ambiguities under Multiple Ambiguities under Multiple InheritanceInheritance
Case 2
Left
int y;
Right
int z;
Bottom
int a
Default inheritance mechanism -> maintains separate copies of the data members inherited from all base classes.
Top
int x;
Ambiguities under Multiple Ambiguities under Multiple InheritanceInheritance
Case 2 (Non-virtual Base class)
Left
int y;
Right
int z;
Bottom
int a
Top
int x;
Top
int x; Top
int x;
Top
int x;
Left
int y;
Right
int z;
Bottom
int a
Ambiguities under Multiple Ambiguities under Multiple InheritanceInheritance
Case 2 : Virtual Base Class
Left
int y;
Right
int z;
Bottom
int a
Top
int x;
virtual virtual
class Left::public virtual Top{…}class Right::public virtual Top{…}
Top
int x;Left
int y;
Right
int z;
Bottom
int a
Ambiguities under Multiple Ambiguities under Multiple InheritanceInheritance
Case 2 : Virtual Base Class
Left
int y;
Right
int z;
Bottom
int a
Top
int x;
virtual virtual
Inherently ambiguous!!!
Ex) Bottom b;
b.x -> b.Left::x?
b.Right::x?
b.Top::x?
Ambiguities under Multiple Ambiguities under Multiple InheritanceInheritance
Case 2 : Virtual Base Class
Left
int y;
Right
int z;
Bottom
int a
Top
int x;
virtual virtual
Assignment for Top::x happens twice.
- Bottom->Left->Top
- Bottom->Right->Top
Ambiguities under Multiple Ambiguities under Multiple InheritanceInheritance
Case 2 : Virtual Base Class
Left
int y;
Right
int z;
Bottom
int a
Top
int x;
virtual virtual
Assignment for Top::x happens twice.
- Bottom->Left->Top
- Bottom->Right->Top
Solution???
General GuidelinesGeneral Guidelines
Use multiple inheritance judiciously. Before using virtual base classes, understand them
thoroughly.– Use an experimental program to understand its
behavior. If a public base class does not have a virtual
destructor, no derived class should have a destructor.
If a multiple inheritance hierarchy has any destructors, every base class should have a virtual destructor.
General GuidelinesGeneral Guidelines
Declare a copy constructor and an assignment operator for classes with dynamically allocated memory.
Prefer initialization to assignment in constructors. List members in an initialization list in the order in
which they are declared. Make sure base classes have virtual destructors. Have operator= return a reference to *this. Assign to all data members in operator=. Check for assignment to self in operator=. Overloading vs. default argument.
General Guidelines : EfficiencyGeneral Guidelines : Efficiency
Don’t guess. Use an execution profiler to isolate performance problems. – g++ : use ‘–pg’. Then use ‘gprof’.– For Visual C++, use professional or enterprise
editions
Optimize your code as much as you can at a source level.– Optimization options (‘-O2’ in g++) provided
by a compiler could be another choice. But it has limitation.
General Guidelines : EfficiencyGeneral Guidelines : Efficiency Consider using lazy evaluation.
– Defer computations until the results of those computations are required.
Understand the origin of temporary objects.– Implicit type conversion– Object return from a function
Overload to avoid implicit type conversions. Facilitate the return value optimization. Consider using ‘op=’ instead of stand-alone ‘op’.
– Provide both in your class. Consider alternative libraries.
Efficiency : ExampleEfficiency : Example
First Design– Number of objects created : 15000001– Elapsed Time : 1.3 sec.
int main(void){ Test_Int a(102); a.report(); cout << a << endl; for(int i=0;i<500000;i++) for(int j=0;j<10;j++) a = a + 1;
a.report();cout << a << endl;a.report(); }
int main(void){ Test_Int a(102); a.report(); const Test_Int one=1; cout << a << endl; for(int i=0;i<500000;i++) for(int j=0;j<10;j++) a = a + one;
Efficiency : ExampleEfficiency : Example
Second Design– Number of objects created :– Elapsed Time :
a.report();cout << a << endl;a.report(); }
int main(void){ Test_Int a(102); a.report(); const Test_Int one=1; cout << a << endl; for(int i=0;i<500000;i++) for(int j=0;j<10;j++) a = a + one;
Efficiency : ExampleEfficiency : Example
Second Design– Number of objects created : 10000002 (67%)– Elapsed Time : 0.74 sec. (57%)
a.report();cout << a << endl;a.report(); }
int main(void){ Test_Int a(102); a.report(); const Test_Int one=1; cout << a << endl; for(int i=0;i<500000;i++) for(int j=0;j<10;j++) a += one;
Efficiency : ExampleEfficiency : Example
Third Design– Number of objects created : – Elapsed Time :
a.report();cout << a << endl;a.report(); }
Test_Int Test_Int::operator+=(const Test_Int& t){ return a+=t.a;}
int main(void){ Test_Int a(102); a.report(); const Test_Int one=1; cout << a << endl; for(int i=0;i<500000;i++) for(int j=0;j<10;j++) a += one;
Efficiency : ExampleEfficiency : Example
Third Design– Number of objects created : 5000000 (57%)– Elapsed Time : 0.43 sec. (58%)
a.report();cout << a << endl;a.report(); }
Test_Int Test_Int::operator+=(const Test_Int& t){ return a+=t.a;}
Efficiency : ExampleEfficiency : Example
Fourth Design– Number of objects created : – Elapsed Time :
Test_Int& Test_Int::operator+=(const Test_Int& t){ add(t); return *this;}
void Test_Int::add(const Test_Int& t){ a += t.a;}
Efficiency : ExampleEfficiency : Example
Fourth Design– Number of objects created : 2 (0.00004%)– Elapsed Time : 0.3 sec. (70%)
Test_Int& Test_Int::operator+=(const Test_Int& t){ add(t); return *this;}
void Test_Int::add(const Test_Int& t){ a += t.a;}
Efficiency : ExampleEfficiency : Example
Fourth Design (relative to the first design)– Number of objects created : Almost nothing.– Elapsed Time : 23%
Test_Int& Test_Int::operator+=(const Test_Int& t){ add(t); return *this;}
void Test_Int::add(const Test_Int& t){ a += t.a;}