CIS-255 Home http://www.c-jump.com/bcc/c255c/c255syllabus.htm
Within a member function, the this keyword is a pointer to the current object.
Current object is the object through which the function was called.
|
class Point { // member variables int m_x; int m_y; public: // member function void adjust( int x, int y ) { this->m_x += x; // Ok, but redundant this->m_y += y; } }; |
Because this is a pointer to the current object, *this refers to the object itself.
Sometimes we want a member function to return a reference to the object it was called on.
String s1; String s2; String s3; s1.append( s2 ).append( s3 ); |
String& String::append( String& other ) { // ... return *this; // "return myself" }
|
Classes can have references as members.
Reference replaces pointer and adds design constraints we may need.
Reference member must be initialized early.
In fact, before constructor body is entered!
New syntax for constructors: the initializer list. Consider:
class Point { private: Graph& m_g; int m_x; int m_y; public: Point( Graph& g, int x, int y ); };
// point.h class Point { private: Graph& m_g; int m_x; int m_y; public: Point( Graph& g, int x, int y ); }; |
|
Preferred constructor syntax by most professionals for member initialization.
Initializer list must be used for:
Function calls OK, but be careful - object is not yet constructed. (Also could be a decent test of debugger quality!)
|
// point.h class Graph { /*...*/ }; class Point { private: Graph& m_g; int m_x; int m_y; public: Point( Graph& g, int x, int y ); }; // point.cpp Point::Point( Graph& g, int x, int y ) : m_x( x ), m_y( y ), m_g( g ) // out of order { } // main.cpp void main() { Graph gr; Point pt( gr, 10, 20 ); } |
main.cpp: In constructor 'Point::Point(Graph&, int, int)': main.cpp:9: warning: 'Point::m_g' will be initialized after main.cpp:7: warning: 'int Point::m_x' main.cpp:20: warning: when initialized here |
// point.h
class Graph { /*...*/ };
class Point {
private:
Graph& m_g;
int m_x;
int m_y;
public:
Point( Graph& g, int x, int y );
};
// point.cpp
Point::Point( Graph& g, int x, int y )
: m_g( g ),
m_x( x ),
m_y( m_x ) // UH-OH... m_x could still be garbage,
{} // so m_y might get it, too!
// main.cpp
void main()
{
Graph gr;
Point pt( gr, 10, 20 );
}
Nice demo of GIGO principal in action: garbage in, garbage out!
Some things shouldn't change while program is running:
const double PI = 3.14159;
Invites compiler optimization
Prevents stupid bugs
Preferred over #define or literals
const is a type modifier
const variables must be initialized
Few good reasons to use const reference in function arguments:
Avoids copying overhead, saving space and time
Preserves caller's peace of mind
Makes altering object (through reference) a compiler error.
Should always be the first choice when passing non-mutable objects:
void print_point( Point const& pt )
Pointer should probably be the first choice when passing mutable objects. Some people argue that programs should never use non-const references for readability reasons.
Must be initialized in init list of each constructor
May be different for different objects
static const members are different
Ok to initialize integral types in declaration
External initialization required for other types
class IntStack { static const int STACK_SIZE = 1024; int m_stack[ STACK_SIZE ]; };
// point.h class Point { static const double PI; }; // point.cpp // Note that static keyword here is an error! const double Point::PI = 3.14159;
Older compilers (think older C++ programs) may not support initialization of integral static variables in declaration.
The usual external static member initialization compiles fine, but still can't be used for array sizes.
Use the "enum hack" instead:
class IntStack { enum { STACK_SIZE = 1024 }; // nameless enum int m_stack[ STACK_SIZE ]; // works fine! };
Consider free function myfunc( )
void myfunc( String const& str );
What should myfunc( ) be allowed to do with the str object?
|
// String.h class String { public: int length() const; void append( char* s ); private: int m_length; int m_bufSize; }; // String.cpp int String::length() const { return m_length; } void myfunc( String const& str ) { int len = s.length(); // Ok s.append( "blah" ); // Compiler error } |
The rule is simple: only const member function calls are acceptable for const objects.
The const member also constrains class writer:
Illegal to modify class data in const member functions
Illegal to call non-const member functions
But...
...Sometimes such constraints are too tight !
|
// String.h class String { public: void reserve( int size ) const; private: mutable int m_buffer_size; mutable char* m_ptr_buffer; }; // String.cpp void String::reserve( int size ) const { if( m_buffer_size < size ) { // reallocate buffer memory, then copy // current string there, then... m_buffer_size = size; // OK, because // m_buffer_size declared mutable } } void myfunc( String const& str ) { str.reserve( 1024 ); // Ok } |
So, what does keyword mutable mean ?
The mutable keyword can only be applied to non-static and non-const data members of a class. If a data member is declared mutable, then it is legal to assign a value to this data member from a const member function.
void main() { double d = 3.5; const double CD = 4.5; double* ptr; // pointer to double double const* ptr2cd; // pointer to const double ptr = &d; // OK ptr = &CD; // Error: address of const can't be assigned to non-const pointer ptr2cd = &d; // OK ptr2cd = &CD; // OK }
void main() { double d = 3.5; const double CD = 4.5; // const ptr to double double *const cptr = &d; // OK double *const cptr = &CD; // Error: not const dbl double *const cptr; // Error: must initialize // const ptr to const double const double* const cptr2 = &d; // OK const double* const cptr2 = &CD; // OK const double* const cptr2; // Error: must initialize }
Interface specifications make promises to clients.
Sometimes, the right specification is not obvious.
The compiler can help you keep these promises...
...and help keep the clients honest, too.
Minimize use of "workarounds" like mutable.