A not very well known design pattern is the PassKey pattern which is mainly used to restrict access to certain public functions (the option would be to use friend in the main classes and that produces too much coupling).
A basic implementation could be:
template<class T>
class PassKey
{
friend T;
PassKey()
{ }
PassKey(PassKey const&)
{ }
PassKey& operator=(PassKey const&) = delete;
};
Note that all elements are private... the trick is that only the type T
will be able to create objects of this type. An example of use to see it better:
class Objeto
{
public:
// Puede ser llamada por cualquiera
void FuncionPublica()
{ }
// Solo Autorizado puede llamar a esta funcion
void FuncionRestringida(PassKey<Autorizado>)
{ }
private:
// Nadie puede llamar a esta funcion
void FuncionPrivada()
{ }
};
class Autorizado
{
public:
void Func(Objeto& obj)
{
obj.FuncionPublica(); // ok
obj.FuncionRestringida(PassKey<Autorizado>()); // ok
obj.FuncionPrivada(); // error de compilacion -> esperado
}
};
class Espia
{
public:
void Func(Objeto& obj)
{
obj.FuncionPublica(); // ok
obj.FuncionRestringida(PassKey<Autorizado>()); // error de compilacion
obj.FuncionRestringida(PassKey<Espia>()); // error de compilacion
obj.FuncionPrivada(); // error de compilacion -> esperado
}
};
Well, now that the context is explained, let's get down to business. Now it turns out that it Objeto.FuncionRestringida()
has to be accessed by two different classes:
class Objeto
{
public:
// Solo Autorizado y OtraClase pueden llamar a esta funcion
void FuncionRestringida(PassKey<Autorizado,OtraClase>);
};
... in theory the problem is not too difficult... just expand the template. let's see:
Version 1
The template now supports two types:
template<class T1, class T2 = void>
class PassKey
{
friend T1;
friend T2;
PassKey()
{ }
PassKey(PassKey const&)
{ }
PassKey& operator=(PassKey const&) = delete;
};
Problem... the copy constructor requires the received object to be of type PassKey<Autorizado,OtraClase>
, which forces you to modify so much Autorizado
as OtraClase
to create the object of the correct type... too cumbersome:
class Autorizado
{
public:
void Func(Objeto& obj)
{
obj.FuncionRestringida(PassKey<Autorizado,OtraClase>()); // ok pero engorroso..
obj.FuncionRestringida(PassKey<OtraClase,Autorizado>()); // error de compilacion
}
};
The idea should be that each one cares to create a key with its type and create some mechanism that does the relevant conversions:
PassKey<T1> -> PassKey<T1,T2>
PassKey<T2> -> PassKey<T1,T2>
version 2
We try to overload the constructors to do the conversions:
template<class T1, class T2 = void>
class PassKey
{
friend T1;
friend T2;
PassKey()
{ }
PassKey(PassKey const&)
{ }
PassKey(PassKey<T1> const&)
{ }
PassKey(PassKey<T2> const&)
{ }
PassKey& operator=(PassKey const&) = delete;
};
Problem... compilation errors occur:
error: multiple overloads of 'PassKey' instantiate to the same signature 'void (const PassKey<Autorizado> &)'
PassKey(PassKey<T1> const&)
^
note: in instantiation of template class 'PassKey<Autorizado, void>' requested here
obj.FuncionRestringida(PassKey<Autorizado>());
^
note: previous declaration is here
PassKey(PassKey const&)
^
error: multiple overloads of 'PassKey' instantiate to the same signature 'void (const PassKey<OtraClase> &)'
PassKey(PassKey<T1> const&)
^
note: in instantiation of template class 'PassKey<OtraClase, void>' requested here
obj.FuncionRestringida(PassKey<OtraClase>());
^
note: previous declaration is here
PassKey(PassKey const&)
error: 'PassKey<T1, T2>::PassKey(const PassKey<T2>&) [with T1 = void; T2 = void]' cannot be overloaded
error: with 'PassKey<T1, T2>::PassKey(const PassKey<T1>&) [with T1 = void; T2 = void]'
In member function 'void Autorizado::Func(Objeto&)':
error: initializing argument 1 of 'void Objeto::FuncionRestringida(PassKey<Autorizado>)'
At this point... is there a solution to the problem?
The first problem in this case is that there is a possible duplicate function:
What happens when this line is executed:
An attempt is made to instantiate an object of type
PassKey<Autorizado,void>
, resulting in the following interface:The copy constructor causes duplicity that is not supported by the compiler. It is not easy to disable the copy constructor based on
T1
yT2
... and given the purpose of this object (throwaway key) the most sensible thing is to remove the copy constructor:Now, depending on the chosen compiler, the solution may or may not compile. Some compilers have trouble solving the problem discussed in the question...
Where does that come from
T1 = void; T2 = void
?Perhaps it is not something that is observed at first sight but there it is. The following object:
It has the following interface:
If we expand the templates by putting both types explicitly:
And voilá, we have already found the implementation
T1 = void; T2 = void
, but... Where is the problem?If we review the implementation of the specialization
<void,void>
, we have the following:Wait... where do these two constructors come from if we had already fixed this problem in the beginning?
Let's look at our current implementation of the template:
The error comes from the two commented constructors. Being
T1 = T2
both constructors become equal. This problem is solved by creating a specialization so<void,void>
that it removes this ambiguity:Although we can also choose to create a slightly more generic solution:
And with this we already managed to overcome all the problems of a solution as apparently simple as the one raised in the question.