A short member function can easily have its body included in its class definition. As soon as the function gets longer, or a class happens to have a bunch of member functions, the body of the class can get awfully wordy and messy. C++ allows us to write member functions as a declaration, which is included in the class so that the compiler knows the function exists, and a definition, which is written outside the class and linked to the class with the scope resolution operator.
Here is a complete program that uses the out of class form:
#include <iostream>
class Counter
{
private:
int value{};
public:
// Declare increase before code needs to call it
void increase();
void print();
};
void Counter::increase()
{
++value;
}
void Counter::print()
{
std::cout << value << '\n';
}
int main()
{
Counter c{};
c.increase();
c.increase();
c.print();
return 0;
}
In class form, all that the programmer writes is the declarations of increase and print: void increase(); and void print();. These end in semicolons and have empty bodies. The actual bodies are written below, where class definitions are no longer being produced.
The definition of increase starts with the void return type, then the name of the class to which it belongs, Counter, then the scope resolution operator ::, the name of the function, increase, and an empty parameter list.
The body which follows this is exactly the same as it would have been had we put it inside the class, which is why the private data member value can be referred to simply by name. The same pattern applies to the function print.
The purpose of the scope resolution operator Counter:: is to connect the definition to the class from which it comes. If you were to forget the Counter::, the compiler would treat increase as if it were any other free function, and then report value is an undeclared identifier. The scope resolution operator explicitly tells the compiler to look inside the class Counter for information concerning increase, and since it recognizes it as a member function of Counter, it allows you to name any of the public and private members of that class.
It's interesting to note that the calls to the functions, like c.increase(); and c.print();, look the same, whether their bodies were written inside or outside of the class. The object on the left hand of the dot is still c and the function continues to operate on the data owned by c.
If a member function takes parameters or returns a non-void value, then the same principle applies: the declaration simply lists the parameters, or the return type, or both, inside the class definition. The out of class definition restates this information, along with a function body.
#include <iostream>
class Rectangle
{
private:
double width{};
double height{};
public:
void set(double w, double h);
double area();
};
void Rectangle::set(double w, double h)
{
// Require both conditions joined by logical AND to be true
if (w > 0.0 && h > 0.0)
{
width = w;
height = h;
}
}
double Rectangle::area()
{
return width * height;
}
int main()
{
Rectangle r{};
r.set(3.0, 4.0);
std::cout << r.area() << '\n';
return 0;
}
For example, declaration void set(double w, double h); merely tells the compiler that a member function called set exists which takes two double parameters and returns a void. The subsequent definition, void Rectangle::set(double w, double h), must exactly match in terms of return type, function name, and parameter types and order. A discrepancy will cause a compile-time error.
The declaration names two double parameters, but in the body, width and height refer to the private members.
An example declaration for a return type of non-void is double area(); with its definition double Rectangle::area(). Once again, the definition repeats the return type, class and function name, and parameters, none in this case, but provides a function body that returns a double as requested.
When you use the split form, it's crucial that you define the class before any of its member function definitions. A definition like void Counter::increase() should appear after class Counter has been defined; otherwise, the compiler has not yet seen the name Counter and generates an "Unknown class" error. Once the class is completely defined, all of its member function definitions can appear in any order: void print() can come before or after void increase(), for instance.
It's easy to fall into a trap and write only void increase() after defining class Counter.
void increase()
{
// Increment the value before the expression uses it
++value;
}
This will trigger two errors: it will get an "undeclared identifier" for value because it assumes increase is a free function and, on top of that, it will be unrecognized as a member of Counter. Another common oversight is to let the argument and return types in the declaration differ from the definition, maybe by dropping or adding a parameter or by making a slight change in its type. Such a mismatched definition is treated by the compiler as a completely separate function, leading it to think that the member that was declared in the class was never defined.
0 comments
No comments yet. Be the first.