COMP2004
Programming Practice
2002 Summer School
Kevin Pulo
School of Information Technologies
University of Sydney
(page 1)
Operator Overloading
- Makes classes act like built-in types
- It has both good and evil uses
- The key is not to overuse it
- Using for output is almost always good
- Allows any class to be output:
- List list;
- std::cout << list << std::endl;
(page 2)
Output Operator Overloading
- Not a class method, ie. an external function:
class List {
// ...
};
ostream& operator<<(ostream &os,
const List &list) {
// ...
}
(page 3)
Output Operator Overloading
ostream& operator<<(ostream &os,
const List &list) {
Node *c = list.head;
if (c) {
os << c.number;
c = c->next;
}
for (; c; c = c->next)
os << ' ' << c.number;
return os;
}
(page 4)
Friend functions
- This requires access to List internals
- Classes can declare functions as being friends
- The function can then access class internals
class List {
// ...
friend ostream& operator<<
(ostream &os, const List &list);
};
(page 5)
Better Design
- If a function doesn't need to be a friend it shouldn't be
- If the class internals change, then friend functions might also have to
- OO is supposed to stop this by hiding internals
- operator<< shouldn't need to be a friend
(page 6)
Operator<<
ostream& operator<<(ostream &os,
const List &list) {
List::ConstIterator b = list.begin(),
e = list.end();
if (b != e) {
os << *b;
++b;
}
for (; b != e; ++b)
os << ' ' << *b;
return os;
}
(page 7)
Converting types
- Converting other types to List
- Define a constructor with a single parameter, eg:
List::List(const std::string &s);
- Also acts as user-defined conversion
- No need for corresponding assignment operator
(page 8)
Conversion operators
- Converting List to other types
List::operator bool() const {
return head;
}
- No return type (since it's implied)
- Works for other types and classes too
- Easy to run into problems though
- Particularly memory leaks
- Usually due to implicit conversions
(page 9)
Conversion operator example
List::operator bool() const {
return head;
}
int main() {
List l;
if (l)
cout << "List has stuff" << endl;
else
cout << "List is empty" << endl;
}
(page 10)
Inheritance
- Inheritance in C++ is a bit complicated
- The defaults are usually not what you want
(page 11)
class Alarm {
public:
void turn_on() {
std::cout << "Alarm on\n";
}
};
class BuzzerAlarm : public Alarm {
public:
void turn_on() {
std::cout << "Buzzer on\n";
}
};
(page 12)
What Is Output?
void activate(Alarm a) {
a.turn_on();
}
int main() {
BuzzerAlarm b;
activate(b);
}
(page 13)
A Problem
- It is called slicing
- The Alarm part of b is passed
- The BuzzerAlarm part is not
- This is almost always an error
- We can stop slicing by passing by reference or pointer
(page 14)
First Attempt At Fix
void activate(Alarm &a) {
a.turn_on();
}
void activate(Alarm *a) {
a->turn_on();
}
(page 15)
More Problems
- It still doesn't work
- Slicing no longer occurs
- Now we have a binding problem
- We want dynamic (or late) binding
- C++ defaults to compile time (or early) binding
- We can fix this too
(page 16)
Fixed Alarm class
class Alarm {
public:
virtual void turn_on() {
std::cout << "Alarm on\n";
}
};
(page 17)
Virtual
- A virtual function uses runtime lookup
- Also known as dynamic dispatch
- It is slower and uses more memory
- On mono we have:
- 1.6 times as long as a normal call
- class is 4 bytes larger
- But it lets you use OO techniques, ie:
- BuzzerAlarm b;
- Alarm &a = b;
- a.turn_on();
(page 18)
Accessibility and Inheritance
- private members are not accessible by derived classes
- protected members are accessible by derived classes
- Provides an interface for derived classes
(page 19)
Accessibility Example
class Base {
public: int i;
protected: int j;
private: int k;
};
class Child : public Base {
void test() {
i = 1; // legal
j = 1; // legal
k = 1; // ERROR
}
};
(page 20)
Constructors and Inheritance
- Base class constructors must be called (manually)
- This is done with initialisers
- If you don't the default constructor will be called
(page 21)
Constructors Example
class Person {
public:
Person(const std::string &name);
};
class Student : public Person {
public:
Student(const std::string &name, const
std::string &sid) : Person(name) {
// ...
}
};
(page 22)
Destructors and Inheritance
- Destructors are automatically called
- Non-virtual destructors can cause problems
- All classes which may have child classes should have virtual destructors
(page 23)
Abstract Classes
- Separating interface from implementation is useful
- In C++ this can be done with abstract classes
- An abstract class is a class with at least one pure virtual method
- Can still have normal methods and variables
- Can't create objects of abstract classes
(page 24)
Abstract Example
class Alarm {
public:
virtual void turn_on() = 0;
virtual void turn_off() = 0;
virtual bool is_on() = 0;
virtual ~Alarm() { };
};
(page 25)
Multiple Inheritance
- C++ supports multiple inheritance
- Can be useful - often leads to problems
- Common base classes can cause problems
- virtual inheritance solves most problems
- You don't need MI for the assignments
- It won't be in the exam either
(page 26)
Non-public Inheritance
class B : private A
- public and protected members of A are private in B
- Doesn't affect the B class itself
- Just other classes
- Including derived classes
class B : protected A
- public members of A are protected in B
- Again doesn't affect B itself
(page 27)
Non-public Inheritance Uses
- Allows inheritance of implementation but not interface
- Following code not allowed
class A { };
class B : private A { };
B b;
A *a = &b;
- The object b is not of type A
(page 28)
Namespaces
- Along with classes C++ also provides namespaces
- Namespaces provide a way to make logical groupings
- Standard library items are placed in namespace std
- Namespaces allow name reuse
(page 29)
Namespace Example
namespace a {
std::string func() { return "a func\n"; }
}
namespace b {
std::string func() { return "b func\n"; }
}
std::string func() { return "func\n"; }
int main() {
std::cout << a::func() << std::endl;
std::cout << b::func() << std::endl;
std::cout << func() << std::endl;
}
(page 30)
Using Namespaces
- It's possible to "pull in" names from a namespace
namespace A { int fred = 10; }
int fred = 20;
int main() {
std::cout << fred << std::endl;
using A::fred;
std::cout << fred << std::endl;
std::cout << A::fred << std::endl;
}
(page 31)
Using Namespaces II
- Entire namespaces can be pulled in
namespace A {
int fred = 10;
double bill = 10.0;
}
int main() {
using namespace A;
std::cout << fred << ' ' << bill;
std::cout << std::endl;
}
(page 32)
Unnamed Namespaces
- Namespaces can be used to hide data
- Not generally very useful
namespace {
int number;
void function() { ... }
}
namespace ??? { ... }
using namespace ???;
(page 33)
Namespace Aliases
- Namespaces reduce name clashes
- For namespace names:
- Very short names may clash
- Long names are a pain to type
- Aliases give the best of both worlds
- Long names - less clashes
- Short aliases - no need for lots of typing
(page 34)
Alias Example
namespace A_Name_Which_Is_Long {
int foo = 42;
}
namespace short =
A_Name_Which_Is_Long;
int main() {
std::cout << short::foo << std::endl;
}
(page 35)