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

Buy: Generative AI in C++: Coding Transformers and LLMs

Generative AI in C++ The new AI programming book by Aussie AI co-founders:
  • AI coding in C++
  • Transformer engine speedups
  • LLM models
  • Phone and desktop AI
  • Code examples
  • Research citations

Get your copy from Amazon: Generative AI in C++