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++ |