Aussie AI
Bypass interfaces with friend functions
-
Book Excerpt from "Generative AI in C++"
-
by David Spuler, Ph.D.
Bypass interfaces with friend functions
Using friend functions may be faster because they can bypass class getter and setter member functions.
If a class declaration has a good deal of private data, it is common C++ style to declare an
interface of public member functions to access private data. Although the class interface
can be quite efficient if member functions are declared as inline, the need to call a
function to access a data value can still make it inefficient in some cases. The use of
friend functions and friend classes can be efficient because this bypasses the class
interface. For example, a member function to set a data member may perform some
range checking on the value, but if we can be sure that a particular function will not use
incorrect data, a friend function can be used to bypass this checking.
friend functions (or friend classes) should not be considered unless the function needs
very fast access to data members, and the member functions to access the data perform
other computations. Note that a member function, with its special privileges, also
bypasses the class interface (because it is part of it), and friend functions should not be
used where member functions would be more appropriate. Programming style is the
consideration here, as they would both have similar efficiency.
A good example of friend function efficiency occurs when an operator function
operates on two different classes, such as when we need an operator
that multiplies a Matrix object
by a Vector object to yield a new Vector.
Assume that both classes have member
functions to access individual elements of the Vector or Matrix.
Consider the declaration of the multiply function as neither a class member nor a friend function, as in:
const int N = 10; // Number of elements in vector/matrix
class Vector {
double data[N];
public:
double get_element(int i) const { return data[i]; }
void set_element(int i, double value) { data[i] = value; }
};
class Matrix {
double data[N][N];
public:
double get_element(int i, int j) const { return data[i][i]; }
};
Vector operator * (const Matrix& m, const Vector& v)
{
Vector temp;
// multiply matrix by vector
for (int i = 0; i < N; i++) { // for each row
double sum = 0.0; // sum of N multiplications
for (int j = 0; j < N; j++) {
sum += m.get_element(i, j) * v.get_element(j);
}
temp.set_element(i, sum); // store new vector element
}
return temp; // return new vector
}
This will be horribly inefficient because the operator*() function must go through both class
interfaces to access elements. Although it isn’t necessarily any less efficient here, if
range checking of the array index i were present in the member functions to set or access
the elements, this would cause inefficiency.
Note that if the Vector class overloaded
the [] operator instead of using a get_element member function, this would make no
difference to efficiency—notational convenience is gained but the operator[]
function has the same cost as any other function.
One alternative to consider is to make the operator* function a member of the
Vector class, but this will still mean using the interface for the Matrix class.
A more
efficient solution is to make the operator* function a friend of both Matrix and
Vector classes, thus allowing it direct access to their individual data elements,
bypassing any range checking on array indices. The more efficient version, using a
friend function, is:
const int N = 10; // Number of elements in vector/matrix
class Matrix;
class Vector {
double data[N];
public:
friend Vector operator * (const Matrix& m, const Vector& v);
};
class Matrix {
double data[N][N];
public:
friend Vector operator * (const Matrix& m, const Vector& v);
};
Vector operator * (const Matrix& m, const Vector& v)
{
Vector temp;
// multiply matrix by vector
for (int i = 0; i < N; i++) { // for each row
double sum = 0.0; // sum of N multiplications
for (int j = 0; j < N; j++) {
sum += m.data[i][j] * v.data[j]; // access data directly
}
temp.data[i] = sum; // store new vector element
}
return temp; // return new vector
}
The disadvantage of using friend functions is the same as their advantage: they pierce class encapsulation.
Because a friend function directly makes use of hidden private data members,
and any change to the class may require a change to the definition of the friend
function, whereas in the first version of the operator* function the use of the
“get_element” member functions of both Vector and Matrix meant that it would
need no changes, provided the “get_element” functions were correctly changed within the class.
|
• Next: • Up: Table of Contents |
|
The new AI programming book by Aussie AI co-founders:
Get your copy from Amazon: Generative AI in C++ |