Friends Friends friend
class MyType {… friend class Other; …};
grants type Other
access to private members of MyType
class MyType {… friend void print(MyType const&); …};
grants free-standing function print
access to private members of MyType
Different Levels of Encapsulation / Data Hiding
public
members / member functions can be accessed by all functions or typesprivate
data can only be accessed by member functions of the same typefriend
allows a limited number of functions/types access to private members
Use Cases
- split an abstraction into several friend types with (mutual) private access
- keep members hidden from any other type/function except for a select few friends
- write free-standing friend functions that can act like member functions
- prevent implicit argument conversions
Scenario / Requirements
- access current state of a simulation from different threads, network processes, …
- at any given time only a subset of results is valid and should be readable from a simulation
- handle situations when simulation no longer exists but other processes still want to access it
- need a way to regulate/restrict (concurrent) access to simulation objects
- simulation data should be private to (almost) all other functions & types
Solution: 2 Types (Simulation + View)
class Simulation {
// grant views access to private data:
friend class SimulationView;
// hidden state …
public:
// only ctor & dtor are public
Simulation(Settings const&); // init & run
~Simulation(); // finalize
};
class SimulationView {
public:
// connect to simulation object
explicit SimulationView(Simulation const*);
// functions for observing simulation go here
…
};
By splitting simulation in 2 classes:
- multiple, independent views per simulation
- views can control and possibly defer access to a simulation
- views can also handle requests even if simulation object no longer exists
- view objects are the only way to access simulation data
- internal state of
Simulation
can be kept hidden from all other types
#include <iostream>
class Point2d {
double x_; // private!
double y_;
public:
explicit Point2d (double x, double y): x_{x}, y_{y} {}
double x () const { return x_; }
double y () const { return y_; }
// can access private members of Point2d:
friend std::istream& operator >> (std::istream& is, Point2d& p) {
return is >> p.x_ >> p.y_;
}
// stream output
friend std::ostream& operator << (std::ostream& os, Point2d const& p) {
return os << "(" << p.x_ << "," << p.y_ << ")";
}
};
int main () {
Point2d p {0,0};
std::cout << "enter 2 integers: ";
std::cin >> p; //
std::cout << p << '\n';
}
operator >>
as defined here
- is a free-standing (operator) function and not a member function
- is declared and defined in the scope of class
Point2d
- has access to all private members of
Point2d
Functions taking an argument of type T are effectively part of T's interface, just like regular member functions.
Preventing Implicit Conversions No Implicit Conversions No Conversions
Friend functions like foo
that are declared in the
scope of a class A
can only be found, if one of the arguments in
the call expression actually is an object of type A
:
class B { };
class A { public:
A () {}
A (B) {} // implicit
A (int) {} // implicit
friend void foo (A) { }
friend void foo (B) { }
friend void foo (int) { }
};
void bar (A) { }
int main () {
A a1{ };
A a2{ B{} };
A a3{ 47 };
bar( A{} );
// implicit conversions:
bar( B{} ); // bar( A{B{}} )
bar( 123 ); // bar( A{123} )
// friend 'foo':
foo( A{} );
// can't find foo(B) & foo(int),
// no implicit conversions to 'A'
foo( B{} ); // compiler error
foo( 123 ); // compiler error
}
A a1{ }; //
A a2{ B{} }; //
A a3{ 47 }; //
bar( A{} ); //
// implicit conversions:
bar( B{} ); // bar( A{B{}} )
bar( 123 ); // bar( A{123} )
// friend 'foo':
foo( A{} ); //
// can't find foo(B) & foo(int),
// no implicit conversions to 'A'
foo( B{} ); //
foo( 123 ); //
Example
class unit_ratio { public:
constexpr explicit
unit_ratio (int denominator);
// NO inverted(unit_ratio) on purpose!
…
};
class ratio {
…
public:
explicit
ratio (int numerator, int denominator=1);
// implicit conversion from unit_ratio:
ratio (unit_ratio r);
…
friend ratio inverted (ratio a) { … }
};
int main () {
ratio x {3,7};
ratio y = inverted(x); // y: 7/3
// no accidental implicit conversions:
unit_ratio fifth {5};
// does not compile:
auto five = inverted(fifth); // ⇒ good!
}
Because ratio
objects can be implicitly constructed from
unit_ratio
objects, any free-standing non-friend function
taking ratio
as input (by value or non-const reference)
could also be called with unit_ratio
objects.
If inverted(ratio)
had been declared in global
scope (instead of as friend inside of ratio
),
we could have used it to call inverted
with unit_ratio
objects despite the fact that we explicitly did not want that!
In general, implicit conversions (like the one from
unit_ratio
to ratio
) are not a good idea.
They can create accidental bugs and unnecessary runtime/memory
overhead.
Comments…