Friends - who needs them?

Friends - who needs them?

By Francis Glassborow

Overload, 3(6):, March 1995


Getting rid of friends

I am writing this article because I find myself out of step with just about every book and article that I have seen published on the subject of using the friend access facility in C++. It seems that there must be something that I have missed or else many of those masters of C++ that I respect share a blind-spot. Of course the situation is compounded by all those who dash into print without an original thought to their name. If you think that last remark is a bit strong, I think that it is long past time that some of the big name authors of books on C from the mid-eighties gave their word-processors a rest and returned to writing some code. When they have done that they just might be qualified to lecture the rest of us on writing genuine C++ rather than C for C++ compilers.

Actually, in many cases their C code is pretty tatty and has a strong odour of the methods of the late seventies when it was considered quite normal to use implicit conversions between ints and any and all kinds of pointers.

Self taught programmers often rely heavily on the code they find in books as being good examples of how a language should be written. It may be slightly amusing that the classic 'Hello World' program lacks a return statement but that is not an excuse for continuing to promulgate such sloppy coding. Properly written C/C++ functions should have explicit return statements marking all exits from a function. I am not an adherent to the school of programming that demands a single exit point for each function as I believe that causes a contorted coding style that is often difficult to maintain. However I do believe that every terminal point in a function should be marked with a return statement even if the programmer believes that no legal path will ever finish there.

Now having alienated any friends that I may have among those writing about C and C++, I can happily proceed with the topic of this article in the happy certainty that many will leap to correct the errors in my thinking without bothering to wrap it up in gentle phrasing - if you want the truth from someone, make them mad.

Typical use of friend

For the purposes of this article I am going to use a trivially simple ADT. I am not going to distract you with all the unnecessary fripperies but provide just enough flesh to make my point.

Record is a class that encapsulates two pieces of information, a name and a number (you can assign any significance to these two items that you like. Here is a pretty minimal interface/implementation:

class Record {
private:
  int number;
  char * name;
protected:
  void put(int i){number=i; return;}
  void put(char const * const cp){
    delete [] name;
    name=new char[strlen(cp)+1];
    strcpy(name,cp);
    return;
  }
  int get() const { return number;}
  void get(char * cp) const {
    // check that cp is a null
    // pointer before changing it
    assert (!cp);
    cp=new char[strlen(name)+1];
    strcpy(cp,name);
    return;
  }
public:
  Record(int i=0; char * cp=" ")
  : number(i), name(0){
    put(cp);
  }
  ~Record(){delete[] name; return;}
}

Please do feel free to write and criticise the above code, but do so constructively by presenting your alternatives with a rationale. In order to do so you will need to decide why I have chosen to provide a protected interface for my access functions while keeping the data strictly private. The implication of the protected interface is that I intend to permit and support derivation from Record. Why do you think that I have avoided the common practice of making my data protected? This is another place that I disagree with most other writers.

So far my class Record provides access to classes derived from it and provides a constructor and destructor. I hope you pencilled in the lack of a copy constructor and overloaded assignment when doing your code inspection. If you missed that you really need to be more careful with other people's code. In this case I think that neither should be implemented because I do not like duplication of records in databases. So both should be declared private and not implemented (for the inexperienced this means that attempts to either copy or assign objects of type Record outside the implementation of Record will be caught by the compiler, while attempts in code implementing Record will be detected by the linker.

I almost wrote in-class and out-of-class in the last sentence but this would not be true. Not all code implementing a user defined type will be in the class. There is more to developing a type than just writing a class interface and implementation.

Printing yourself

It would seem fairly attractive to provide a mechanism whereby a Record can send itself to an output stream. At this stage most writers comment that it would be nice to arrange that our user defined type should have the same ability that built-in types have - output by using operator << and an ostream object. Then they promptly dig a hole and bury themselves.

They carefully (and accurately) explain why we cannot provide a new overloaded version of operator << inside the Record class. The critical operand (left-hand one) is the wrong type, one over which we have no rights. The consequence is that the new overload of operator << must be provided in global space (I will avoid the complications of namespaces until they are more widely implemented). This causes a problem of access to Record data. What they all finish up with is placing the following line in class Record:

friend
ostream&
operator<<(ostream& out, const Record& r);

Why? I just cannot imagine. It is quite unnecessary. Worse still, as we shall see, it is a poor solution for any class where the designer intends to allow derivation and possible late binding.

What do I do? Look at the following prototype for a public member function.

void print(ostream& out = cout) const;

Implement it any way you want. As it is an output function you probably do not want to fatten your code by making it inline, but do so if you must. Having done this, you can either implement an overloaded version of operator << yourself or leave it up to users of your code to do so. The implementation is about as simple as you can get:

ostream&
operator<<(ostream& out, const Record& r) {
  r.print(out);
  return out;
}

All the functionality you had before but no abuse of friendship. If this is as far as it went I would not be quite so disturbed by what I read but ...

friend and polymorphism do not mix

Of course my class Record is pretty skimpy both in data and functionality. I can be certain that I am going to want to use it as a base class. In real life, I would have written an abstract base class (ABC) with no data and only pure virtual member functions to provide an interface. But getting completely abstract inhibits some programmers ability to understand.

Go back to my class Record and make the destructor virtual, at the same time add virtual to the prototype for print(). Do you see the result? Exactly, I now have a polymorphic (late binding) implementation of operator << for my hierarchy of classes derived from Record.

Surely this must be the right way to go? Using 'friend' to fix up the access problem is not only unnecessary but positively gets in the way of finding an effective object-oriented solution.

Mixins and printable

There is one further step to take down the path to full OO programming. You need to grasp the concept using multiple inheritance with ABC's to implement a style that is called mixin programming (in memory of American Ice Cream vendors - British programmers might use the term "pick 'n mix"). Consider the following minimalist class:

class Printable {
public:
  virtual void print(
  ostream& out = cout) const = 0;
  virtual ~Printable(){};
}

Printable is an ABC that does nothing other than encapsulate the concept of being printable. The reason that I have included a virtual destructor is because sometime someone is going to want to create a collection of printable objects.

Now consider the effect of:

ostream&
operator<<(ostream& out, const Printable& p)
{
  p.print(out);
  return out;
}

If we want to provide any class with the facility to use an overloaded operator << we have to ensure that it has Printable as a base class and a member function void print(ostream&).

Once you grasp the value of this type of ABC, one that provides the interface for a specific capacity that a user defined type may have you are ready to use a mixin style.

Multiple inheritance is an essential ingredient for such a style. For example another essential property for class Record would be persistence (the ability to write and read itself to/from backing storage). In this case you might decide that the ABC had some data. For example you might decide that it is a property of persistent objects that they know if they have been archived.

Now our class Record needs two independent base classes, and we can see how we need to have multiple inheritance available. For example:

class Record: public Printable, public Storable
{
// class interfaces
}

Inexperienced programmers often argue that they can provide the functionality of base classes by substituting aggregation or layering for inheritance. That is by providing a Printable and a Storable member of class Record. It should be clear that this will not work for mixin programming. The mixin elements are ABCs and so objects of exactly that type can not exist (though, of course, objects derived from them can). Much more important than this restriction (which could be fixed by removing the pure virtual functions) is the fundamental need for late binding which cannot be provided other than by inheritance.

If you are simply intending to use multiple inheritance to build the interface of the proper base for your hierarchy then the above methodology will meet all your needs. In reality, once multiple inheritance is available it will be used for other purposes - because it is actually useful and not just a fad. This leads to the problem of cases such as:

class X1
: public isPrintable1, public anotherPrintable
{
// class interfaces
}

Now X1 has two base classes Printable. The introduction of virtual base classes exactly fixes this problem with very little overhead if the virtual bases are ABCs with minimalist interfaces.

Almost always when you are using mixins, they should be virtual public base classes. You should not feel reluctant to use this style. Multiple inheritance, virtual bases and ABCs are a powerful mix that all programmers should have in their toolkit of programming methods.

Like friend access, multiple inheritance, virtual bases and ABCs are useful but should not be hijacked to do things that do not need them. Such hijacking produces code that is difficult to maintain and probably does not do what you want.

Work with your language

One of the things we teach dinghy sailors is to learn to work with the elements and not try to fight them. If your boat constantly resists you then you are probably trying to achieve your aim by inappropriate methods. The eventual result is a 'swimming lesson'.

If you constantly seem to be struggling with your chosen programming language it is either the wrong language for the problem domain or you do not understand how to use it. The result of such a struggle is poor code, riddled with defects which is hard to maintain.

Learn to work with the tools of your language to express coherent solutions to your problems.

Now perhaps the 'friend' aficionados will stand up and explain why I am wrong. They must have an explanation because many of them take considerable sums of money from the ordinary punters for books that use it to supply global operators.

Even worse are those that charge large 3 figure (or even four figure) fees to teach you C++ and still teach 'friend' as the way to go.

There are important uses for friend, without which much code could not be written in a clear and effective fashion but that is another article or two.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.