15

Is it possible to declare a member function of a forward-declared class as friend? I am trying to do the following:

class BigComplicatedClass;

class Storage {
   int data_;
public:
   int data() { return data_; }
   // OK, but provides too broad access:
   friend class BigComplicatedClass;
   // ERROR "invalid use of incomplete type":
   friend void BigComplicatedClass::ModifyStorage(); 
};

So the goal is to (i) restrict the friend declaration to a single method, and (ii) not to include the definition of the complicated class to reduce compile time.

One approach might be to add a class acting as an intermediary:

// In Storage.h:
class BigComplicatedClass_Helper;
class Storage {
    // (...)
    friend class BigComplicatedClass_Helper;
};

// In BigComplicatedClass.h:
class BigComplicatedClass_Helper {
     static int &AccessData(Storage &storage) { return storage.data_; }
     friend void BigComplicatedClass::ModifyStorage();
};

However, this seems a bit clumsy... so I assume that there must be a better solution!

hrr
  • 1,807
  • 2
  • 21
  • 35
  • possible duplicate of [How to declare a friend that is a member function of another not yet defined class in C++?](http://stackoverflow.com/questions/4355660/how-to-declare-a-friend-that-is-a-member-function-of-another-not-yet-defined-clas) – Ben Voigt Jun 10 '11 at 18:54
  • Thanks for the reference -- I saw that question; however its accepted answer is the too-broad class-level access that I wanted to avoid... – hrr Jun 10 '11 at 19:46

3 Answers3

13

As @Ben says, it's not possible, but you can give specific access just to that member function through a "passkey". It works a bit like the intermediate helper class, but is imho clearer:

// Storage.h
// forward declare the passkey
class StorageDataKey;

class Storage {
   int data_;
public:
   int data() { return data_; }
   // only functions that can pass the key to this function have access
   // and get the data as a reference
   int& data(StorageDataKey const&){ return data_; }
};

// BigComplicatedClass.cpp
#include "BigComplicatedClass.h"
#include "Storage.h"

// define the passkey
class StorageDataKey{
  StorageDataKey(){} // default ctor private
  StorageDataKey(const StorageDataKey&){} // copy ctor private

  // grant access to one method
  friend void BigComplicatedClass::ModifyStorage();
};

void BigComplicatedClass::ModifyStorage(){
  int& data = storage_.data(StorageDataKey());
  // ...
}
Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • Nice! Admittedly, it took me a while to understand why the copy-constructor has to be private... – hrr Jun 10 '11 at 19:50
  • @hrr: Well, it doesn't have to be, since you can still pass the key around as references if you want to. – Xeo Jun 10 '11 at 20:11
  • If the copy-constructor were public, unprivileged users could get a key too easily: `StorageDataKey *key_ptr = 0; StorageDataKey key(*key_ptr);` thus making the copy-constructor private seems to make much sense. – hrr Jun 12 '11 at 18:14
  • @hrr: Good point on the nullpointer dereference. It is undefined behaviour, though it works so long you don't access any members (which isn't the case anyways here). The problem, you can't just change the paramter to `data` to a non-reference `StorageDataKey`, as you'd need the full definition for that. :/ In C++0x, you could take an rvalue reference `StorageDataKey&&`, though I think that can be bypassed too with a simple `std::move`... Hm. – Xeo Jun 12 '11 at 18:19
  • @hrr making it private doesn't solve the problem, the user could call `storage_.data(*key_ptr)`. I think `StorageDataKey` should have `int const id`, which should be checked in `Storage ` class before grating access. – q126y Nov 21 '15 at 04:54
5

No, you can't declare individual member functions as friends until they've been declared. You can only befriend the entire class.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
1

It may or may not be relevant here, but it is useful to remind ourselves that there is a wild world beyond the scope of classes and objects where functions can roam free.

For example, I recently needed to close off a (singleton global static) system error log from a global exception handler based on a port of someone else's code. The normal include file for my error log conflicted with the exception handler code because both wanted to include "windows.h" for reasons I didn't look into. When this and other questions persuaded me I could not make a forward declaration of my ErrorLog class's member functions, what I did was wrap the necessary functions into a global scope function like this:

void WriteUrgentMessageToErrorLog( const char * message )
{
  ErrorLog::LogSimpleMessage( message );
  ErrorLog::FlushAccumulatedMessagesToDisk();
}

Some people are very particular about maintaining the integrity of their class structure at all cost... and seldom acknowledge that applications using those classes are inevitably built on top of something that lacks that structure. But it's out there, and used judiciously, it has its place.

Given the age of this question, I have not looked deeply into its relevance here. All I wanted to share was the opinion that sometimes a simple wrapping mechanism like this is a much cleaner and more readily understood alternative to something that has a lot more subtlety and cleverness about it. Subtlety and cleverness tends to get changed at some later date by someone required to add to it who didn't fully understand it. Before you know it, you have a bug...

omatai
  • 3,448
  • 5
  • 47
  • 74