Improving Friends with Attributes

1,414 views
Skip to first unread message

marton...@gmail.com

unread,
May 18, 2018, 5:52:27 AM5/18/18
to ISO C++ Standard - Future Proposals, Zoltan Porkolab
Hi,

The purpose of this mail is to provide an initial brief description of the idea and to gauge the level of interest.



*) Introduction
The friend language construct is one of the oldest element in C++ and often criticised.
Some people claim that friendship breaks the encapsulation, reflects bad design and creates too strong coupling, thus they advise to avoid it.
However, friends appear even in the most carefully designed systems, and if they are used judiciously they may be a better choice than widening the public interface of a class.
Please see our research paper for details (http://gsd.web.elte.hu/papers/2018/selective-friends_spe_2018.pdf).



*) Selective Friends
Empirical studies show that friend functions (and especially friend classes) use only a low percentage of the private members they were granted to have access to [1, 2].
Meyers states the encapsulation is greater if the number of functions that can access the private parts of the class is fewer [3, Item 23].
Similarly, we state that the encapsulation is greater if the number of accessible private entities is fewer because in that case the accessible private part of the class is smaller.
We propose a new attribute [[selective_access]] that indicates that the attributed friend is intended to access only a certain list of data members, member functions or nested types of the befriending class.
If the given friend accesses other private or protected entities than those listed by the attribute, the compiler is encouraged to issue a warning.
Example:
    class A {
      int x = 0;
      int y = 0; // expected-note
                 // {{implicitly declared private here}}

      friend void func(A &a) [[selective_access(x)]]; // expected-note
                                                      // {{ access restricted to certain members }}
    };
    void func(A &a) {
      a.x = 1; // OK
      a.y = 1; // expected-warning
               // {{'y' is a private member of 'A'}}
    }
A proof-of-concept implementation is publicly available at https://github.com/martong/clang/tree/selective_friend .

C++17 compliant compilers are required to silently ignore unknown attributes, thus tools which do not support the new attribute would just simply ignore it.
Supporting compilers can provide better diagnostics and warn the user.
By realizing this feature as an attribute, backward compatibility is guaranteed and no syntax change is needed in the grammar.
Note, there has been a previous proposal with a bit similar semantics, but with a very different syntax [4].

Alternatives:
- The Access Key Idiom [8, 9]. The drawback of this pattern is that we cannot handle the accessing of member fields with it, we can handle only member functions.
- The Attorney-Client Idiom [10]. The use of this idiom does not scale well, because we would need to define several attorney classes if we wanted to provide access for the different combination of members. Also, using too many attorneys might result in unmaintainable and hardly understandable code.

Other languages;
- The Eiffel programming language uses a special tagging mechanism for every class member to achieve selective friend like access control. For each member we may provide a list of classes which can have access to the member.



*) Const Friends
Const correctness prevents us from inadvertently modifying something we didn't expect would be modified.
There are certain cases when a friend function accesses the private or protected members only in a read-only manner.
For instance, a friend stream output operator (<<) to dump the content of a class does not modify the state of the class.
If that function was a member function then we would make that a const member function.
This could be especially important in case of friend classes.
We propose a new attribute [[const_access]] that indicates that the attributed friend is intended to have read-only access.
If the given friend accesses private or protected entities as lvalues, the compiler is encouraged to issue a warning.
Example:
    class A {
      int x = 0;
      int y = 0;
      friend [[const_access]] class B; // read-only
    };
    class B {
      void func(A &a) { // expected-warning, B has const access only
        int i = a.x;
        a.x = 1; // or expected-warning here
      }
    }
By realizing this feature as an attribute, backward compatibility is guaranteed and no syntax change is needed in the grammar.

Friend is an explicit mechanism for granting access, just like membership.
Member functions and friend functions are equally privileged, they access every innards of a class.
The major difference is that a friend function is called like f(x), while a member function is called like x.f() [5, 6].
Stroustrup states that during the language design, a friendship declaration was seen as a mechanism similar to that of one protection domain granting a read-write capability to another.
It is an explicit and specific part of a class declaration [7, 2.10].
Thus, we conclude if there are const member functions then there should be const friends as well.
We consider this argument so strong that alternatively to the attribute syntax we propose to introduce a syntax change in the core language and enable the possibility to declare a friend as const.
Example:
    class A {
      int x = 0;
      int y = 0;
      friend class B const; // read-only
    };



*) Out-of-class Friends
White-box testing is a method of testing software that tests internal structures or workings of an application.
This kind of testing requires accessing the internals of a class, thus the friend language element is an appropriate tool to provide access for these tests.
People often declare a test accessor class in the unit under test:
    Class A {
      friend class TestAccessor;
      // ...
    }
However, often we cannot modify or do not want to modify the original class to inject a friend declaration just because of testing.
We propose a new attribute [[friend_for]] that indicates that the attributed free function has access to the specified class private or protected entities.
Compiler vendors should be encouraged to disable this attribute by default so users should explicitly require this feature and they should use it only for the purpose of white-box testing but not in production code.
Example:
    class A {
      int a = 0;
    };
    [[friend_for(A)]] void func(A &a) {
      a.a = 1;
    }
A proof-of-concept implementation is publicly available at https://github.com/martong/clang/tree/out-of-class_friend_attr

Alternatives to out-of-class friends:
- #define public private (and #define class struct). The standard explicitly prohibit this, people still do it.
- Exploit explicit template instantiation to gain access (https://github.com/martong/access_private). Besides of being a verbose solution, some compilers do not implement the relevant part of the standard, thus this works only with GCC.
Other languages:
- Java package private



*) Combined attributes
The above attributes may be used not just individually, but combined.
Example:
    class A {
      int x = 0;
      int y = 0;
      friend [[const_access]][[selective_access(x)]] class B; // read-only access of x only
    };



Sincerely,
Gabor Marton



[1] English M,Buckley J,Cahill T.A friend in need is a friend indeed [software metrics and friend functions].Empirical Software Engineering, 2005. 2005 International Symposium on, IEEE, 2005; 10–pp.
[2] Márton G, Porkoláb Z. Selective friends in C++. Softw Pract Exper. 2018;1–27.
[3] Meyers S. Effective C++: 55 Specific Ways to Improve Your Programs and Designs (3rdEdition). Addison-Wesley Professional, 2005. Item 46, p. 256.
[5] isocpp org. C++ FAQ, Friends 2016. URL https://isocpp.org/wiki/faq/friends, accessed: 2016-06-12.
[6] Stroustrup B. The C++ programming language. Pearson Education, 2013.
[7] Stroustrup B. The Design and Evolution of C++. ACM Press/Addison-Wesley Publishing Co.: New York, NY, USA, 1994. P. 53.

Gábor Márton

unread,
Mar 6, 2019, 5:04:51 AM3/6/19
to ISO C++ Standard - Future Proposals
Ping
> --
> You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
> To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/wtkDvujmjug/unsubscribe.
> To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
> To post to this group, send email to std-pr...@isocpp.org.
> To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/25e60cac-91d7-4197-9a08-ce8aa90e67ab%40isocpp.org.

Balog Pal

unread,
Mar 18, 2019, 7:59:48 AM3/18/19
to ISO C++ Standard - Future Proposals, zpo...@gmail.com, marton...@gmail.com


2018. május 18., péntek 11:52:27 UTC+2 időpontban marton...@gmail.com a következőt írta:
Example:
    class A {
      int x = 0;
      int y = 0; // expected-note
                 // {{implicitly declared private here}}

      friend void func(A &a) [[selective_access(x)]]; // expected-note
                                                      // {{ access restricted to certain members }}
    };
    void func(A &a) {
      a.x = 1; // OK
      a.y = 1; // expected-warning
               // {{'y' is a private member of 'A'}}
    }


I like the idea, and can confirm that I could use such extension in my code. I agree with the statement that friend is a useful facility and that most often we grant it for a single item or a small subset of the class.

OTOH the cases it cover are pretty rare and the extra protection is not likely to catch a real problem. The reviews are sufficient for practice and I can recall a strict zero accidents related to friend usage as we have it now.

So this does not make it the "top 20" list of important issues maybe not even the top 2000.   IMHO spending the committee time on this is not feasible. And would not be even without the massive backlog.

OTTH if you have the implementation, I suggest to make it an official pull request in clang and gcc, it is fine as extension. A few years ahead it can be pulled in the standard showing numbers of use.
 
Reply all
Reply to author
Forward
0 new messages