C++ Programming/Classes/Member Functions
From Wikibooks, the open-content textbooks collection
Contents |
[edit] Member Functions
Member functions can (and should) be used to interact with data contained within user defined types. User defined types provide flexibility in the "divide and conquer" scheme in program writing. In other words, one programmer can write a user defined type and guarantee an interface. Another programmer can write the main program with that expected interface. The two pieces are put together and compiled for usage. User defined types provide encapsulation defined in the Object Oriented Programming (OOP) paradigm.
Within classes, to protect the data members, the programmer can define functions to perform the operations on those data members. Member functions and functions are names used interchangeably in reference to classes. Function prototypes are declared within the class definition. These prototypes can take the form of non-class functions as well as class suitable prototypes. Functions can be declared and defined within the class definition. However, most functions can have very large definitions and make the class very unreadable. Therefore it is possible to define the function outside of the class definition using the scope resolution operator "::". This scope resolution operator allows a programmer to define the functions somewhere else. This can allow the programmer to provide a header file .h defining the class and a .obj file built from the compiled .cpp file which contains the function definitions. This can hide the implementation and prevent tampering. The user would have to define every function again to change the implementation. Functions within classes can access and modify (unless the function is constant) data members without declaring them, because the data members are already declared in the class.
Simple example:
file: Foo.h
// the header file named the same as the class helps locate classes within a project // one class per header file makes it easier to keep the header file readable (some classes can become large) // each programmer should determine what style works for them or what programming standards their // teach/professor/employer has #ifndef FOO_H #define FOO_H class Foo{ public: Foo(); // function called the default constructor Foo( int a, int b ); // function called the overloaded constructor int Manipulate( int g, int h ); private: int x; int y; }; #endif
file: Foo.cpp
#include "Foo.h" Foo::Foo(){ x = 5; y = 10; } Foo::Foo( int a, int b ){ x = a; y = b; } int Foo::Manipulate( int g, int h ){ x = h + g*x; y = g + h*y; }
[edit] const
This type of member function cannot modify the member variables of a class. It's a hint both to the programmer and the compiler that a given member function doesn't change the internal state of a class.
Take for example:
class Foo { public: int value() const { return m_value; } void setValue( int i ) { m_value = i; } private: int m_value; };
Here value() clearly does not change m_value and as such can and should be const. However setValue() does modify m_value and as such cannot be const.
Another subtlety often missed is a const member function cannot call a non-const member function (and the compiler will complain if you try). The const member function cannot change member variables and a non-const member functions can change member variables. Since we assume non-const member functions do change member variables, const member functions are assumed to never change member variables and can't call functions that do change member variables.
The following code example explains what const can do depending on where it is placed.
class Foo { public: /* * Modifies m_widget and the user * may modify the returned widget. */ Widget *widget(); /* * Does not modify m_widget but the * user may modify the returned widget. */ Widget *widget() const; /* * Modifies m_widget, but the user * may not modify the returned widget. */ const Widget *cWidget(); /* * Does not modify m_widget and the user * may not modify the returned widget. */ const Widget *cWidget() const; private: Widget *m_widget; };
[edit] static
Member functions or variables declared static are shared between all instances of an object type. This means that only one copy of the member function or variable exists for any object type. Named constructors are a good example of using static member functions. Named constructors is the name given to functions used to create an object of a class C without (directly) using its constructors. This might be used for the following:
- To circumvent the restriction that constructors can be overloaded only if their signatures differ.
- Making the class non-inheritable by making the constructors private.
- Preventing stack allocation by making constructors private
Declare a static member function that uses a private constructor to create the object and return it. (It could also return a pointer or a reference but this complication seems useless, and turns this into the factory pattern rather than a conventional named constructor.) Here's an example for a class that stores a temperature that can be specified in any of the different temperature scales.
class Temperature { public: static Temperature Fahrenheit (double f); static Temperature Celsius (double c); static Temperature Kelvin (double k); private: Temperature (double temp); double _temp; }; Temperature::Temperature (double temp):_temp (temp) {} Temperature Temperature::Fahrenheit (double f) { return Temperature ((f + 459.67) / 1.8); } Temperature Temperature::Celsius (double c) { return Temperature (c + 273.15); } Temperature Temperature::Kelvin (double k) { return Temperature (k); }
[edit] Inline
Sharing most of the concepts we have seen before on the introduction to inline functions, when dealing with member function those concepts are extended, with a few additional considerations.
If the member functions definition is included inside the definition of the class. that function is by default made implicitly inline. Compiler options may also override this behavior.
Calls to virtual functions cannot be inlined if the object's type is not known at compile-time, because we don't know which function to inline.
[edit] Accessors and Modifiers (Setter/Getter)
- What is an accessor?
- An accessor is a member function that does not modify the state of an object. The accessor functions should be declared as const operations.
- Getter is another common definition of an accessor due to the naming ( GetSize() ) of that type of member functions.
- What is a modifier?
- A modifier, also called a modifying function, is a member function that changes the value of at least one data member. In other words, an operation that modifies the state of an object. Modifiers are also known as ‘mutators’.
- Setter is another common definition of an modifier due to the naming ( SetSize( int a_Size ) ) of that type of member functions.
[edit] Lazy initialization
It is always needed to maintain the balance between the performance of the system and the resource consumption. Let us see how to reduce the resource consumption until it is required.
Lazy instantiation is one of the memory conservation mechanism, by which the object initialization is deferred until it is required.
Look at the following example:
class Wheel { int speed; public: int getSpeed(){ return speed; } void setSpeed(int speed){ this->speed = speed; } }; //this must be corrected class Car{ private Wheel wheel = new Wheel(); public int getCarSpeed(){ return wheel.getSpeed(); } public String getName(){ return "My Car is a Super fast car"; } public static void main(String a[]){ Car myCar = new Car(); System.out.println(myCar.getName()); } }
Instantiation of class Car by default instantiates the Class Wheel. The purpose of the whole class is to just print the name of the car. Here the instance wheel doesn't serve any purpose, loading of which is a complete resource waste.
It is better to defer the instantiation of the un-required class until it is needed. Modify the above class Car as follows:
//must be corrected class Car { private Wheel wheel; public int getCarSpeed(){ if(wheel == null){ wheel = new Wheel(); } return wheel.getSpeed(); } public String getName(){ return "My Car is a Super fast car"; } public static void main(String a[]){ Car myCar = new Car(); System.out.println(myCar.getName()); } }
Now the Wheel will be instantiated only when the member function getCarSpeed() is called. This is known as Lazy initialization.
[edit] Overloading
Member functions can be overloaded. This means that multiple member functions can exist with the same name on the same scope, but must have different signatures. A member function's signature is comprised of the member function's name and the type and order of the member function's parameters.
Due to name hiding, if a member in the derived class shares the same name with members of the base class, they will be hidden to the compiler. To make those members visible, one can use declarations to introduce them from base class scopes.
Constructors, other class member functions, except the Destructor, can be overloaded.
[edit] Constructors
A constructor is a special member function which is called whenever a new instance of a class is created. The compiler calls the constructor after the new object has been allocated in memory, and converts that "raw" memory into a proper, typed object.
The constructor is declared as any normal member function but it will share the name of the class. Constructors are responsible for almost all of the run-time setup necessary for the class operation. Its main purpose becomes in general defining the data members upon object instantiation (when an object is declared), they can also also have arguments, if the programmer so chooses. If a constructor has arguments, then they should also be added to the declaration of any other object of that class when using the new operator. Constructors can also be overloaded.
Foo myTest; // essentially what happens is: Foo myTest(); Foo myTest( 3, 54 ); // accessing the overloaded constructor Foo myTest = Foo( 20, 45 ); // although a new object is created, there are some extra function calls involved // with more complex classes, an assignment operator should // be defined to ensure a proper copy (includes ''deep copy'') // myTest would be constructed with the default constructor, and then the // assignment operator copies the unnamed Foo( 20, 45 ) object to myTest
using new with a constructor
Foo* myTest = new Foo(); // this defines a pointer to a dynamically allocated object Foo* myTest = new Foo( 40, 34 ); // constructed with Foo( 40, 34 ) // be sure to use delete to avoid memory leaks
|
NOTE: While there is no risk in using new to create an object, it is often best to avoid using memory allocation functions within objects' constructors. Specifically, using new to create an array of objects, each of which also uses new to allocate memory during its construction, often results in runtime errors. If a class or structure contains members which must be pointed at dynamically created objects, it is best to sequentially initialize these arrays of the parent object, rather than leaving the task to their constructors. |
A constructor can't delegate to another. It is also considered desirable to reduce the use of default arguments, if a maintainer has to write and maintain multiple constructors it can result in code duplication, which reduces maintainability because of the potential for introducing inconsistencies and even lead to code bloat.
- Default Constructors
A default constructor is one which can be called with no arguments. Most commonly, a default constructor is declared without any parameters, but it is also possible for a constructor with parameters to be a default constructor if all of those parameters are given default values.
In order to create an array of objects of a class type, the class must have an accessible default constructor; C++ has no syntax to specify constructor arguments for array elements.
[edit] Overloaded Constructors
When an object of a class is instantiated, the class writer can provide various constructors each with a different purpose. A large class would have many data members, some of which may or may not be defined when an object is instantiated. Anyway, each project will vary, so a programmer should investigate various possibilities when providing constructors.
These are all constructors for a class myFoo.
myFoo(); // default constructor, the user has no control over initial values // overloaded constructors myFoo( int a, int b=0 ); // allows construction with a certain 'a' value, but accepts 'b' as 0 // or allows the user to provide both 'a' and 'b' values // or myFoo( int a, int b ); // overloaded constructor, the user must specify both values class myFoo { private: int Useful1; int Useful2; public: myFoo(){ // default constructor Useful1 = 5; Useful2 = 10; }; myFoo( int a, int b = 0 ) { // two possible cases when invoked Useful1 = a; Useful2 = b; }; }; myFoo Find; // default constructor, private member values Useful1 = 5, Useful2 = 10 myFoo Find( 8 ); // overloaded constructor case 1, private member values Useful1 = 8, Useful2 = 0 myFoo Find( 8, 256 ); // overloaded constructor case 2, private member values Useful1 = 8, Useful2 = 256
[edit] Constructor initialization lists
Constructor initialization lists (or member initialization list) are the only way to initialize data members and base classes with a non-default constructor. Constructors for the members are included between the argument list and the body of the constructor (separated from the argument list by a colon). Using the initialization lists is not only better in terms of efficiency but also the simplest way to guarantee that all initialization of data members are done before entering the body of constructors.
// Using the initialization list for _myComplexMember MyClass::MyClass(int mySimpleMember, MyComplexClass myComplexMember) : _myComplexMember(myComplexMember) // only 1 call, to the copy constructor { _mySimpleMember=mySimpleMember; // uses 2 calls, one for the constructor of the mySimpleMember class // and a second for the assignment operator of the MyComplexClass class }
This is more efficient than assigning value to the complex data member inside the body of the constructor because in that case the variable is initialized with its corresponding constructor.
Note that the arguments provided to the constructors of the members do not need to be arguments to the constructor of the class; they can also be constants. Therefore you can create a default constructor for a class containing a member with no default constructor.
Example:
MyClass::MyClass() : _myComplexMember(0) { }
It is useful to initialize your members in the constructor using this initialization lists. This makes it obvious for the reader that the constructor does not execute logic. The order the initialization is done should be the same as you defined your base-classes and members. Otherwise you can get warnings at compile-time. Once you start initializing your members make sure to keep all in the constructor(s) to avoid confusion and possible 0xbaadfood.
It is safe to use constructor parameters that are named like members.
Example:
class MyClass : public MyBaseClassA, public MyBaseClassB { private: int c; void *pointerMember; public: MyClass(int,int,int); }; /*...*/ MyClass::MyClass(int a, int b, int c): MyBaseClassA(a) ,MyBaseClassB(b) ,c(c) ,pointerMember(NULL) ,referenceMember() { //logic }
Note that this technique was also possible for normal functions but it is now obsoleted and is classified as an error in such case.
|
NOTE: One should also keep in mind that class members are initialized in the order they are declared, not the order they appear in the initializer list. One way of avoiding chicken and egg paradoxes is to always add the members to the initializer list in the same order they're declared. |
[edit] Destructors
Destructors like the Constructors are declared as any normal member functions but will share the same name as the Class, what distinguishes them is that the Destructor's name is preceded with a "~", it can not have arguments and can't be overloaded.
Destructors are called whenever an Object of the Class is destroyed. Destructors are crucial in avoiding resource leaks (by deallocating memory), and in implementing the RAII idiom. Resources which are allocated in a Constructor of a Class are usually released in the Destructor of that Class as to return the system to some known or stable state after the Class ceases to exist.
The Destructor is invoked when Objects are destroyed, after the function they were declared in returns, when the delete operator is used or when the program is over. If an object of a derived type is destructed, first the Destructor of the most derived object is executed. Then member objects and base class subjects are destructed recursively, in the reverse order their corresponding Constructors completed. As with Structs the compiler implicitly-declares a Destructor as a inline public member of its class if the class doesn’t have a user-declared Destructor.
The dynamic type of the object will change from the most derived type as Destructors run, symmetrically to how it changes as Constructors execute. This affects the functions called by virtual calls during construction and destruction, and leads to the common (and reasonable) advice to avoid calling virtual functions of an object either directly or indirectly from its Constructors or Destructors.

