CIS-255 Home http://www.c-jump.com/bcc/c255c/c255syllabus.htm
class interface
data members and member functions
headers and implementation source files
scope issues to consider
access control: public and private members
access control: exceptions for friends
struct Point { int x; // member variables int y; void adjust( int x_, int y_ ) // member function { x += x_; y += y_; } }; void main() { Point p; // instantiate object p.x = p.y = 4; // access member variables p.adjust( 1, 1 ); // call member function }
data members provide object state
member functions provide:
public interface
internal implementation procedures
struct Point { int x; // member variables int y; void adjust( int x_, int y_ ); // member functions void draw( Plane* plane_ ); };
//////////////////////////////////////////////////////// // point.h struct Point { int x; // member variables int y; void adjust( int x_, int y_ ); // member declaration }; //////////////////////////////////////////////////////// // point.cpp #include "point.h" void Point::adjust( int x_, int y_ ) // member definition { x += x_; // Note: access to member variables is easy y += y_; }
C++ variables, functions, and classes are placed in a scope.
Scope determines visibility and lifetime of variables.
C++ comes with:
global scope outside functions (global namespace)
local scope inside functions
In C++, 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 also makes code harder to understand).
Some partial solutions:
Avoid using global scope when possible;
Use a naming convention to indicate the scope of variables;
Also note class member naming convention:
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 the same variable, like a global, but in the class scope:
// point.h class Point { static int m_x_origin; static int m_y_origin; int m_x; int m_y; //... }; // point.cpp: int Point::m_x_origin = 0; // initialization of private (!) static members int Point::m_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.
Static member functions don't have a this pointer.
And they cannot refer to non-static members in any way.
// point.h class Point { static int m_x_origin; static int m_y_origin; static void set_origin( int x_, int y_ ); //... }; // point.cpp void Point::set_origin( int x_, int y_ ) { m_x_origin = x_; m_y_origin = y_; }
#include "point.h" void main() { // Correct, but confusing: // suggests that p is somehow involved: Point p; p.set_origin( 0, 0 ); // Better: Point::set_origin( 0, 0 ); }
Demo: download and run this EXE, then click
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 tries (in order):
exact match, promotion, and conversion.
void print( char c_ ); void print( int i_ ); void print( char* s_ ); void f() { print( 'g' ); // exact match with char print( 3 ); // exact match with int short x; print( x ); // promote 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 compiler...
...by making violations become compiler errors
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.
// point.h class Point { private: int m_x; int m_y; void reset_coord(); public: double distance_to_origin(); }; |
// main.cpp void main() // free function { Point p; p.m_x = 4; // Error, m_x is private p.reset_coord(); // Error, reset_coordinates() is private double d = p.distance_to_origin(); // Ok, public } |
// point.cpp 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 ); } |
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 public;
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.
Therefore, if you accept free "gifts" from the compiler
(read: implicit interface)...
...be sure you really want them!
Sometimes two classes need to work together.
Sometimes a function needs to be global, but also needs to have access to internal data.
For example:
class Point { friend class Window; friend void print_point( Point* p ); friend void circle::set_radar_radius( Point* p ); //... };
A friend does get 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!