sdm@cs.brown.edu (Scott Meyers) (08/02/90)
Proposed: That it be possible to declare member functions outside of class declarations exclusively for the purpose of later friend declarations. Example: class Example; // currently legal void Example::f(int); // currently illegal, but would be ok class Sample { friend void Example::f(int); // currently legal, provided class // Example has been defined function() { void (Example::*pmf)(int) = Example::f; // currently illegal and would // remain illegal } }; Motivation: Such a change would make it easier to declare certain friend relationships without having to embed #include directives in the middle of header files. As an example, consider the following classes: - WithFriend, which has a private int member and which declares the constructor for class Derived as a friend. - Base, which has a constructor that takes an int as an argument. - Derived, a derived class of Base, that has a constructor that takes a WithFriend reference as an argument and initializes the Base constructor with the private int member of the referenced WithFriend. This must currently be declared as follows: class Base { protected: Base(int baseArg) {} }; class WithFriend; class Derived: public Base { public: Derived(WithFriend& derivedArg); }; class WithFriend { friend Derived::Derived(WithFriend&); private: int privateMember; }; inline Derived::Derived(WithFriend& derivedArg) : Base(derivedArg.privateMember) {} With the proposed change, it could be declared as follows: class WithFriend; extern Derived::Derived(WithFriend&); // currently illegal class WithFriend { friend Derived::Derived(WithFriend&); private: int privateMember; }; class Base { protected: Base(int baseArg) {} }; class Derived: public Base { public: Derived(WithFriend& derivedArg) : Base(derivedArg.privateMember) {} }; The latter declaration is a little cleaner, in my opinion, because all the forward declarations are at the beginning and there is no need to separate the inline body of the Derived constructor from the class declaration, but that's hardly compelling. The important situation -- and the common one -- is when WithFriend is declared in a different file from Base and Derived, say WithFriend.h and Base.h, respectively. Then the declaration must be as follows: class Base { protected: Base(int baseArg) {} }; class WithFriend; class Derived: public Base { public: Derived(WithFriend& derivedArg); }; #include "WithFriend.h" inline Derived::Derived(WithFriend& derivedArg) : Base(derivedArg.privateMember) {} In this case the #include directive must occur in the middle of the file, contrary to intuition and common convention for #include file usage. With the proposed change, the #include directives need only occur at the top of files: class WithFriend; // file WithFriend.h extern Derived::Derived(WithFriend&); class WithFriend { friend Derived::Derived(WithFriend&); private: int privateMember; }; ------------------------------------------------------------------------- #include "WithFriend.h" // file Base.h class Base { protected: Base(int baseArg) {} }; class Derived: public Base { public: Derived(WithFriend& derivedArg) : Base(derivedArg.privateMember) {} }; Note that allowing member functions to be declared but not used is similar to the standard C++ facility for allowing class names to be declared but no objects of those classes to be used prior to class definition. Scott sdm@cs.brown.edu
howell@bert.llnl.gov (Louis Howell) (08/02/90)
In article <46393@brunix.UUCP>, sdm@cs.brown.edu (Scott Meyers) writes: |>Proposed: |> |> That it be possible to declare member functions outside of class |> declarations exclusively for the purpose of later friend declarations. |> |>Example: |> |> class Example; // currently legal |> void Example::f(int); // currently illegal, but would be ok |> |> class Sample { |> friend void Example::f(int); // currently legal, provided class |> // Example has been defined |> <stuff deleted here> |> }; Why should even this be necessary? The friend declaration contains all of the information necessary to deduce the declarations of Example and Example::f(int), so why not omit these declarations too? Perhaps I'm missing something here, but I don't see why it should be illegal to declare a member function of a class not yet defined to be a friend. You can declare a non-member function to be a friend whether that function has been declared or not; likewise with an entire class. Why the special restriction for member functions? Here's a particular case that's caused me trouble. Say I have two classes, low_level_object (LLO) and high_level_object (HLO). HLO's are really big things, including arrays of several different kinds of LLO's. Since HLO's are built up out of LLO's and manipulate them extensively, it makes sense to declare the LLO's first, right? In the later stages of development, however, there may be a significant optimization possible by permitting one or two HLO member functions direct access to LLO data. I would like to simply declare those one or two functions friends of the LLO class, but instead I have to declare the whole HLO class to be a friend of the LLO class and throw data hiding out the window. Note that this is the logical direction for friendship to go; no LLO function will ever need access to HLO data or functions, but HLO's manipulate LLO's all the time. Yes, I know I could order things differently. Declare LLO to be a class but don't define it, then define HLO, then define LLO and its inline member functions, then finally define HLO's member functions including the inline ones. Besides being brain damaged, this approach prevents me from defining HLO inline functions that call LLO functions in the HLO class definition. The code is therefore bulkier, harder to read and harder to maintain. Privacy has become counterproductive when it makes it harder to write well structured code. In addition, no such kludge will permit you to define two classes, each of which has a member function of the other as friend. The best you can do is declare one whole class to be a friend of the other. Opinion: I think data hiding is a Good Thing. Occasionally, however, you have to make exceptions to it---hence the friend mechanism. This mechanism should be as narrow and selective as possible, or data hiding will be severely compromised. OK, I'm done. Now everbody all at once tell me why this is stupid and couldn't possibly work. :-) Louis Howell #include <std.disclaimer>
hopper@ux.acs.umn.edu (hopper) (08/03/90)
In article <65729@lll-winken.LLNL.GOV> howell@bert.llnl.gov (Louis Howell) writes: >In article <46393@brunix.UUCP>, sdm@cs.brown.edu (Scott Meyers) writes: >|>Proposed: >|> >|> That it be possible to declare member functions outside of class >|> declarations exclusively for the purpose of later friend declarations. >|> >|>Example: >|> >|> class Example; // currently legal >|> void Example::f(int); // currently illegal, but would be ok >|> >|> class Sample { >|> friend void Example::f(int); // currently legal, provided class >|> // Example has been defined >|> <stuff deleted here> >|> }; > >Why should even this be necessary? The friend declaration contains all >of the information necessary to deduce the declarations of Example and >Example::f(int), so why not omit these declarations too? Stuff deleted. I'm not precisely certain of how this is implemented, but what if you declared a virtual member function of a class, and then use the mechanism proposed in this article to declare it to be a friend? If you called this function, how would the compiler generate the code to select the proper virtual function if it was unaware of the other parts of the class declaration? As I understand it now, the compiler knows how many virtual functions you have, so it just makes a reference to the proper member of an array of function pointers. If the compiler didn't know how many virtual functions had been declared, it would have to somehow pass this task on to the linker. This would mean substantial changes to the linker and it's ability to change references to symbols into hard addresses. At least I think so. Probably just being stupid, UUCP: rutgers!umn-cs!ux.acs.umn.edu!hopper (Eric Hopper) __ /) /**********************/ / ') // * I went insane to * / / ______ ____ o // __. __ o ____. . _ * preserve my sanity * (__/ / / / <_/ / <_<__//__(_/|_/ (_<_(_) (_/_/_)_ * for later. * Internet: /> * -- Ford Prefect * hopper@ux.acs.umn.edu </ #include <disclaimer.h> /**********************/
howell@bert.llnl.gov (Louis Howell) (08/03/90)
In article <1946@ux.acs.umn.edu>, hopper@ux.acs.umn.edu (hopper) writes: |>In article <65729@lll-winken.LLNL.GOV> howell@bert.llnl.gov (Louis Howell) writes: |>>In article <46393@brunix.UUCP>, sdm@cs.brown.edu (Scott Meyers) writes: |>>|>Proposed: |>>|> |>>|> That it be possible to declare member functions outside of class |>>|> declarations exclusively for the purpose of later friend declarations. |>>|> |>>|>Example: |>>|> |>>|> class Example; // currently legal |>>|> void Example::f(int); // currently illegal, but would be ok |>>|> |>>|> class Sample { |>>|> friend void Example::f(int); // currently legal, provided class |>>|> // Example has been defined |>>|> <stuff deleted here> |>>|> }; |>> |>>Why should even this be necessary? The friend declaration contains all |>>of the information necessary to deduce the declarations of Example and |>>Example::f(int), so why not omit these declarations too? |> |>Stuff deleted. |> |> I'm not precisely certain of how this is implemented, but what if |>you declared a virtual member function of a class, and then use the |>mechanism proposed in this article to declare it to be a friend? If you |>called this function, how would the compiler generate the code to select the |>proper virtual function if it was unaware of the other parts of the class |>declaration? More stuff deleted. I'm not sure that I understand what your objection has to do with the idea of friends. I'm not proposing any change to the virtual function mechanism or the required order of declarations. As I understand it, at least, all a friend declaration accomplishes is an override to the privacy rules. I want to be able to tell the compiler to remember an exception of the following form: "The private data (and functions) of this class should be accessible to a function called f(int) in a class called Example." It shouldn't matter if the compiler has ever heard of Example or Example::f(int), all it has to do is remember that a function with that name should have access to private parts of class Sample. If the function is virtual, then a separate friend declaration would be required for the functions Derived_from_Example::f(int), Also_Derived_from_Example::f(int), and so on, if they are also to have special access. Am I missing something here, or are we talking about two different things? Louis Howell #include <std.disclaimer>