CIS-255 Home: http://www.c-jump.com/bcc/c255c/c255syllabus.htm
Things to talk about
interface of a C++ class
data members and member functions
header and implementation source files
scope issues to consider
access control: public and private members
access control: rules for "friends"
Recall class with a function:
// main.cpp class Point { public: // member variables int x; int y; // member functions void adjust( int xx, int yy ) { x += xx; y += yy; } }; // don't forget this semicolon! int main() { // Create object Point pt; // access to member variables pt.x = 5; pt.y = 4; pt.adjust( 1, 1 ); // member function call return 0; }
data members provide object state
member functions provide:
public interface
internal implementation procedures
// main.cpp class Point { public: // member variables int x; int y; // member functions declarations void adjust( int xx, int yy ); void draw( Canvas* cc ); };
//////////////////////////////////////////////////////// // point.h class Point { public: int x; // member variables int y; void adjust( int xx, int yx ); // member declaration }; //////////////////////////////////////////////////////// // point.cpp #include "point.h" void Point::adjust( int xx, int yy ) // member definition { x += xx; // Note: access to member variables is easy y += yy; }
C++ variables, functions, and classes are placed in a scope.
Scope determines visibility and lifetime of a variable.
C comes with:
global scope outside functions (global namespace)
local scope inside functions
code blocks also have their own scope.
In C++, classes provide a scope nested inside the global scope.
Class scopes can be referred to by name using the :: operator.
Member functions themselves are in the class scope.
Member function bodies have the local scope.
Define items inside class scope:
class Point { public: typedef int coordinate; enum quadrant { FIRST, SECOND, THIRD, FOURTH }; class inner_drawing { ... }; //... };
Using items defined within class scope:
Point::coordinate coord = 4; Point::quadrant q1 = Point::FIRST; Point::inner_drawing idraw;
Increasing the nesting of scopes invites shadowing and makes code harder to understand.
Some partial solutions:
Avoid using global scope whenever possible
Use a naming convention to indicate the scope of variables. For example, class member variable naming convention might be:
class Point { int m_x; // Indicate member with m_ prefix int m_y; //... };
Class members may be declared static
Static data members belong to the class, not to any object...
...so they don't take up space in objects.
All objects share static variables just like global variables, but in the class scope:
// point.h class Point { static int x_origin; static int y_origin; int m_x; int m_y; //... }; // point.cpp: int Point::x_origin = 0; // initialization of private (!) static members int Point::y_origin = 0;
Static member functions also belong to the class
Objects can be used to call these functions (but this is confusing.)
Static member functions
can be called without an object
don't have a this pointer (discussed later)
cannot refer to non-static members in any way.
// point.h class Point { static int x_origin; static int y_origin; static void set_origin( int xx, int yy ); //... }; // point.cpp void Point::set_origin( int xx, int yy ) { x_origin = xx; y_origin = yy; }
#include "point.h" int main() { Point pt; // Correct, but confusing: // suggests that pt is somehow involved: pt.set_origin( 0, 0 ); // Better: Point::set_origin( 0, 0 ); return 0; }
Animated demo: download and run cjumpcxx.exe , then click
Objects / Static Members
In C, function names must be unique across all modules.
In C++, function names may be reused, provided the arguments allow disambiguation.
Different classes may have the same member function name, since the type of the object allows the compiler to disambiguate.
Global functions can also be overloaded...
...just as functions within the same class:
class Point { double distance_to_point( point other ); double distance_to_point( int x, int y ); // ... };
Most cases are intuitive and unambiguous.
Some ambiguities can be resolved by the compiler.
If not, the call is an error and needs to be resolved by programmer.
Resolution attempts (in order):
exact match, promotion, conversion.
void print( char ch ); void print( int num ); void print( char* str ); void foo() { print( 'g' ); // exact match with char print( 3 ); // exact match with int short xx = 2; print( x ); // promotes x to int print( 3.4 ); // error: ambiguous call to overloaded function print( "OK" ); // exact match with char* print( NULL ); // exact match with int (!!!) }
Encapsulation means we want to restrict use to a chosen interface...
...and not allow a use based on implementation
Restrictions should be enforced by the compiler...
...by making violations to become compiler errors
The rules of the language prevent accidents, not malice!
New keywords: public and private
Use as many access specifiers as you want, in any order.
Each one is in effect until the next one, or the end of the declaration.
Specifiers go only in class declarations, nowhere else.
// point.h class Point { private: int m_x; int m_y; void reset_coordinates(); public: double distance_to_origin(); };
Member functions can access any other members (functions or data).
Non-members (i.e. clients) can access only public members.
Overload resolution happens before access checking.
protected access will be introduced when we cover inheritance.
// point.h class Point { private: int m_x; int m_y; void reset_coord(); public: double distance_to_origin(); }; // point.cpp #include <cmath> double Point::distance_to_origin() { // Access to private members m_x, m_y is allowed, // because we are in a member function: return sqrt( ( double ) m_x * m_x + m_y * m_y ); } // main.cpp int main() // free function { Point pt; pt.m_x = 4; // Error, m_x is private pt.reset_coord(); // Error, reset_coordinates() is private double dbl = pt.distance_to_origin(); // Ok, public return 0; }
Member access includes members of other objects of our class:
// point.h class Point { private: int m_x; int m_y; void reset_coord(); public: double distance_to_origin(); double distance_to_point(); }; // point.cpp double Point::distance_to_point( Point other ) { // Access to private members m_x, m_y is allowed, // because we are in a member function: double x_diff = m_x - other.m_x; double y_diff = m_y - other.m_y; return sqrt( x_diff * x_diff + y_diff * y_diff ); }
Default access control is:
public for structs;
private for classes.
Common practice is to:
Tag access control by public and private labels in classes.
Use structs as if we were programming in C:
Forget private: everything is public by default
No functions allowed.
If you your struct needs a function, it should probably become a class.
Encapsulation is a good thing:
Makes code more modular by supporting abstraction
Decreases time spent maintaining code
Increases likelyhood of reusability
Makes code easier to understand
Data members give you an implicit interface.
Member functions provide explicit interface carefully chosen by a programmer.
Encapsulation controls both the explicit and the implicit interface to your object.
Keep all data private, provide meaningful public functions
Some novice programmers like to accept free "gifts" from the compiler (read: implicit interface of public data members) which easily becomes a huge problem down the road.
Sometimes
two classes need to work together.
or a free function wants to have access to private data of a class
For exampple,
class Point { friend class Window; friend void print_point( Point* p ); friend void circle::set_radar_radius( Point* p ); //... };
A friend gets full access to private members of the class.
Friendship is a one-way street:
Friendship is not symmetrical.
A class has no access to its friend.
The friend of my friend is still a stranger to me...
...friendship is not transitive.
Suggestions:
Prefer making functions friends rather than classes;
Declare friends at the top of class declarations;
Don't make friends lightly!