This page briefly covers some special functionality of class members. const
functions allow you to work with classes that have been designated as constant, and static
members do not change between class instances.
const Functions
There are times you may want to have a const
class, where data-altering methods cannot be run, but accessors still need to work. An example of a situation where this can be useful is when you want to pass by reference but guarantee the members of the class will not change.
Let's use the Shape
class as an example...
FILE: Shape.hh
#ifndef SHAPE_HH
#define SHAPE_HH
class Point2d {
public:
Point2d();
Point2d(int xValue, int yValue);
int x;
int y;
};
class Shape {
private:
int layer;
Point2d location;
int angleDeg;
protected:
float width;
float height;
public:
Shape();
Shape(float w, float h, Point2d loc, int angle);
void setLayer(int l);
void setWidth(float w);
void setHeight(float h);
void setLocation(Point2d loc);
void chgLocation(Point2d delta);
void setRotation(int degrees);
void chgRotation(int degrees);
int getLayer() const;
float getWidth() const;
float getHeight() const;
Point2d getLocation() const;
int getRotation() const;
virtual float area() const = 0;
virtual float perimeter() const = 0;
};
#endif
All we did was append const
to the end of the function prototypes we want to be able to call from a const
object. If an object is constant, we want to be able to access the data, but not change it.
- Warning
- A
const
at the end of the prototype and at the beginning are different. If const
is at the beginning, the data it returns cannot be changed. If it is at the end, it determines whether the method can be called from a const object.
The implementation has similar changes...
FILE: Shape.cc
...
int Shape::getLayer() const {
return layer;
}
float Shape::getWidth() const {
return width;
}
float Shape::getHeight() const {
return height;
}
int Shape::getRotation() const {
return angleDeg;
}
Point2d Shape::getLocation() const {
return location;
}
- Note
- The constructors don't need to be declared
const
.
I used the class from the inheritance section here in order to show how to deal with const in virtual functions...
FILE: Rectangle.hh
#ifndef RECTANGLE_HH
#define RECTANGLE_HH
#include "Shape.hh"
class Rectangle : public Shape {
public:
Rectangle();
Rectangle(float w, float h, Point2d loc, int angle);
float area() const;
float perimeter() const;
};
#endif
FILE: Rectangle.cc
#include "Rectangle.hh"
using namespace std;
Rectangle::Rectangle() {
}
Rectangle::Rectangle(float w, float h, Point2d loc, int angle) :
Shape(w, h, loc, angle)
{}
float Rectangle::area() const {
return width * height;
}
float Rectangle::perimeter() const {
return (width*2) + (height*2);
}
Example main()
#include <iostream>
#include "Rectangle.hh"
using namespace std;
void print(const Shape& s);
int main() {
Rectangle r;
r.setWidth(2);
print(r);
}
void print(const Shape& s) {
cout << "Angle: " << s.getRotation() << " Layer: " << s.getLayer() << '\n'
<< "Location: " << s.getLocation().x << ' ' << s.getLocation().y << '\n'
<< "Width: " << s.getWidth() << " Height: " << s.getHeight() << '\n'
<< "Area: " << s.area() << " Perimeter: " << s.perimeter();
}
Output
Angle: 0 Layer: 0
Location: 0 0
Width: 2 Height: 1
Area: 2 Perimeter: 6
The only difference in the above program is that print
now does not have the ability to call methods that were not labeled const
. This guarantees that the object will not change.
In order to demonstrate this, consider the code below. It compiles and runs correctly.
#include <iostream>
#include "Rectangle.hh"
using namespace std;
int main() {
const Rectangle r;
cout << r.getWidth();
}
If we try to compile something that runs a method that was not labeled as const
, we will run into trouble.
#include <iostream>
#include "Rectangle.hh"
using namespace std;
int main() {
const Rectangle r;
r.setWidth(2);
cout << r.getWidth();
}
Compiler Error
test.cc: In function ‘int main()’:
test.cc:7:17: error: passing ‘const Rectangle’ as ‘this’ argument of ‘void Shape::setWidth(float)’ discards qualifiers [-fpermissive]
r.setWidth(2);
^
Compilation exited abnormally with code 1
static Members
Static members have one value no matter what the object is. They can be useful for a variety of reasons, but I will use it to count how many instances of a class there are.
static Variables
Here is an example class that uses a static
variable to count how many instances exist.
FILE: CountMe.hh
#ifndef COUNTME_HH
#define COUNTME_HH
class CountMe {
private:
static int inst;
public:
CountMe();
~CountMe();
int getInstances() const;
};
#endif
- Note
- The destructor is run automatically when the class goes out of scope. A destructor is always named
~ClassName
and has no return type. I will explain destructors in more detail later. For now, think of them as the opposite of constructors.
FILE: CountMe.cc
#include "CountMe.hh"
int CountMe::inst = 0;
CountMe::CountMe() {
inst++;
}
CountMe::~CountMe() {
inst--;
}
int CountMe::getInstances() const {
return inst;
}
- Warning
- The static member has to be initialized outside of the class. This can only be done once, and has to happen in the global scope.
FILE: test.cc
#include <iostream>
#include "CountMe.hh"
int main() {
CountMe c[10];
cout << c[0].getInstances() << '\n';
if (true)
{
CountMe d;
cout << d.getInstances() << '\n';
}
cout << c[0].getInstances();
}
Output
10
11
10
static Functions
static
functions are methods that do not operate on a specific object. Also, they cannot access data that is object-specific (any members that are not static
). This basically makes the class behave as if it were a namespace.
Example
The getInstances
method is not useful for specific objects. It gets the total number of instances that exist.
#ifndef COUNTME_HH
#define COUNTME_HH
class CountMe {
private:
static int inst;
public:
CountMe();
~CountMe();
static int getInstances();
};
#endif
- Note
static
methods cannot be declared as const
or virtual
.
The implementation file does not change, but the way you call the method does. Both of the calls below work correctly.
object.getInstance();
CountMe::getInstance();
friend Functions
friend
functions have access to the private members of a class. They are useful when you are doing operations on multiple objects of the same class, and you need private member access. Here is a simple example of where this can be useful.
FILE: Point2d.hh
#ifndef POINT2D_HH
#define POINT2d_HH
class Point2d {
private:
int x;
int y;
public:
Point2d();
bool setX(int xVal);
bool setY(int yVal);
int getX();
int getY();
friend Point2d add(Point2d a, Point2d b);
};
#endif
- Note
- No additional prototype for the friend function is necessary, even though it is not actuall a part of the class.
FILES: Point2d.cc
#include "Point2d.hh"
Point2d add(Point2d a, Point2d b) {
Point2d result;
result.x = a.x + b.x;
result.y = a.y + b.y;
return result;
}
Point2d::Point2d() {
x = 0;
y = 0;
}
bool Point2d::setX(int xVal) {
if (xVal >= 0)
{
x = xVal;
return true;
}
else
return false;
}
bool Point2d::setY(int yVal) {
if (yVal >= 0)
{
y = yVal;
return true;
}
else
return false;
}
int Point2d::getX() {
return x;
}
int Point2d::getY() {
return y;
}
Test File
#include <iostream>
#include "Point2d.hh"
using namespace std;
int main() {
Point2d p1;
Point2d p2;
Point2d res;
p1.setX(1);
p1.setY(2);
p2.setX(3);
p2.setY(4);
res = add(p1, p2);
cout << res.getX() << ' ' << res.getY() << endl;
}
Output
4 6