2

I am looking for a solution to the following problem:

I have a class X with (assuming) only protected members. And I have a set S of several classes A, B, C, ...

Assuming there is an existing instance x of class X members (which instance may be, either an instance of class X, or a subset of an instance of any suitable container/derived-class/... depending on class X) :

  • Any instance s of a class of the set S, in relation with (depending on) x, must have access to protected members of x.
  • Access to protected members of x must be restricted to s (and x).
  • Creation and destruction of instance s must keep instance x alive and unaltered.

Additionally, at a given time, only one instance s [in relation] with x is existing.

In other words and to clarify the above requirements: I need that any instance s have access to protected members of class X, just like if (for example) classes of the set S were publicly derived from X, except that the subset of members coming from class X in inheritance must remain alive and unaltered whether instance s is created or destroyed.

In addition, the following requirements must be met:

  • X must be considered non-copyable and non-movable.
  • A solution involving wrapper to protected members of X, though acceptable, is not desirable due to maintenance cost.
  • Making all classes of set S friends of X is obviously not acceptable (to many classes).

The currently implemented solution, that though does not fulfills requirement #5, is using composition and a parent class for classes of S friend of X, e.g.:

class X
{
    // public: int get_prot();        // not allowed (rq#2)
    protected: int prot;
    friend class Xaxx;
    // friend A; friend B; ...        // not acceptable (rq#6)
};

class Xacc
{
protected:
    Xacc(X& x) : x(x) {}
    int& x_prot() { return x.prot; }  // not desirable (rq#5)
    X& x;
};

class A : public Xacc
{
public:
    A(X& x) : Xacc(x) {}
    void work()  { x_prot() = 1; }
};

Another interesting solution tested, that fulfills all requirements expect #4 though:

class A : public X
{
public:
    A(const X& x) : X(x) {}            // X not copyable (rq#4)
    void work()  { prot = 1; }
};

Any solution up to C++14 is acceptable. Thanks for your help.


Rationale:

To clarify where this problem comes from and in which way a solution will help me improve my code:

  • Every classes of the set S represent states of a state-machine (which state-machine is in some way inspired from the State pattern from the Gang of 4).
  • Each state must have access to a common underlying sub-object (the instance of X) which implements all sort of works (algorithms, i/o, and so on...)
  • When a new state is entered, an object of the proper class in set S is created; when the state is exited, the object is destroyed, then replaced by a new one (a new state). The instance of X must not be altered in this switch.
shrike
  • 4,449
  • 2
  • 22
  • 38
  • 1
    This is the definition of an XY question. What are you actually trying to do? Or is it just "give homework to SO" time lol – Lightness Races in Orbit May 10 '16 at 15:26
  • @Lightness: does this question really look like a homework ? damned ! I leave school for more than 20 years though... What I am trying to do is to solve the problem I made my best to explain in details... Is the solution that obvious ? – shrike May 10 '16 at 15:39
  • Hmm.. members are `protected` in order to, well, protect them from "unauthorized" access. Only X and its derivates know what invariants hold true for their members. *Only X can grant exceptional access* to other classes in the shape of `friend` declarations. If other classes could autonomously gain access that would render `protected` irrelevant. All these rules exist for a reason. So your requirement smells: Perhaps the members are semantically not protected? Or you are looking for an equivalent of C#'s `internal`. One could make a case for that additional level of access for modularization. – Peter - Reinstate Monica May 10 '16 at 15:52
  • No, I said it's the definition of an XY question. I said nothing about it being obvious. – Lightness Races in Orbit May 10 '16 at 15:55
  • @Peter: You can see classes of set S as 'extensions' of X (as if they were derived from X, as I tried to explain), so yes, classes of set S needs exceptional access to X, which exceptional access should not be granted to any other class except the ones from S. – shrike May 10 '16 at 16:14
  • 1
    @Lightness: no offence; maybe I tried too hard to dissect my problem which gave this smell of homework; see edited rationale to know more about where this question comes from. – shrike May 10 '16 at 16:19
  • 1
    @shrike Is the part of the interface of the classes in S uniform and simple? For instance would that be something that could be abstracted out as a specific interface common across all classes in S? – Come Raczy May 10 '16 at 18:38
  • @ComeRaczy I understand that "friendship" is not inherited. So you would like to factor out the access functions to protected X members in a concrete class which is declared a friend in X and have that as a member in all classes in S? – Peter - Reinstate Monica May 10 '16 at 20:38
  • @PeterA.Schneider I was thinking on the contrary of having all the protected access factored out into a template friend class. See my answer below. – Come Raczy May 10 '16 at 20:43

4 Answers4

2

Is making the external class a friend of the class it needs access to out of the question?

http://www.cplusplus.com/doc/tutorial/inheritance/

  • 1
    This should perhaps be a comment, since you are asking for clarification. – jonspaceharper May 10 '16 at 15:16
  • 3
    @JonHarper: I think its a a rhetorical question and thus an answer. Also the correct one. The OP is obviously doing an exercise and just needs a pointer. – Martin York May 10 '16 at 15:19
  • @Dan: thanks for answer but no, friendship is not acceptable in this way because there are too many classes in the set **S** (see requirement #5) – shrike May 10 '16 at 15:30
  • 1
    @Loki: not an exercise, a real problem I have to solve in a real program; thanks for the link, but I'm looking for a pattern, I guess I know how friendship works. – shrike May 10 '16 at 15:32
  • 1
    @shrike: Friends is the answer. You just have not though it through. You don't want to make lots of classes a friend. But you can still get all the classes in `s` to have access by making a single class friend as long a certain conditions are met. Basically you have three legal ways to access protected members. 1) Inheritance. 2) Getting a pointer to a member 3) Friendship. You have ruled out 1 and 2 which only leaves 3. – Martin York May 10 '16 at 15:38
  • 1
    @shrike: If this is a real code the tell us the real details rather than making up stunted class names and phrasing it like hypotheticals. Give us the code for the real problem. English is a bad language to express a program in express it in a programming language. It will be much more concise and meaningful with less ambiguity than English. – Martin York May 10 '16 at 15:45
  • 1
    @Loki: well, maybe something I forgot to mention is that number of classes of S is big and may grow continuously, which disqualifies friendship of all such classes in X, while the single friendship of inherited Xacc still is acceptable because independent on the set of classes of S. – shrike May 10 '16 at 15:47
  • @shrike Previous rhetorical question aside, I'm now unclear. Friendship is only intractable if you're talking an unknown number of classes in set S. So if simple inheritance won't work either, I'm not sure what you're asking. The given defines set S to include classes which are _and are not_ children of class X. Those classes in set S that are children of X require access to X's protected members, which they do in C++ by definition. Since only class X and its descendants (all in set S) will have access to X's protected members, access is restricted to instances of classes in set S. – Dan C. Wlodarski May 10 '16 at 15:50
  • To abbreviate: if not friends, why can't a _class_ S be X's parent? – Dan C. Wlodarski May 10 '16 at 15:52
  • 1
    @shrike: Which is exactly what I was hinting at. You add an interface class that is friends. Then all types from S can inherit from the interface. Thus you are only friending one class. Basic Open/Close principle at work. – Martin York May 10 '16 at 20:40
  • @LokiAstari but as you point out, friendship is not inherited. So I'm not sure what you are suggesting. – Chris Drew May 11 '16 at 04:58
  • @ChrisDrew: You have an interface class that accesses the members you want and exposes an interface to get the values you want. Then anybody that wants accesses inherits from the interface. – Martin York May 11 '16 at 05:07
  • @LokiAstari isn't that just OP's currently solution? (which does not fulfill all their requirements) – Chris Drew May 11 '16 at 05:11
2

If your classes in S have a uniform interface that is simple (e.g. a single 'work' method), you can change your current implementation to fulfill requirement #5 by making Xacc a template class and by moving the implementation of the access to the protected parts of X into the specializations of Xacc. It would look like this:

class X 
{
protected:
    int prot;
    template<typename State> friend class Xacc;
};

template<class State>
class Xacc
{
public:
    Xacc(X &x) : x(x) {}
    void work();
private:
    X &x;
};

class S1;
template<> void Xacc<S1>::work()
{
    x.prot = 1;
};

class S1: public Xacc<S1>
{
public:
    S1(X &x): Xacc<S1>(x) {}
protected:
};
Come Raczy
  • 1,590
  • 17
  • 26
  • I like this suggestion. (1) Would it be worse to let Xacc be a member instead of a base class (delegation instead of derivation)? (2) If Xacc restricts itself to just providing access to `X::prot` (and some kind of `work()`, using `Xacc`'s `X::prot` access, were implemented as simple members in each `S`x) -- then `Xacc` doesn't need to be a template, right? – Peter - Reinstate Monica May 10 '16 at 21:17
  • 1
    @PeterA.Schneider (1) is mostly a question of personal preference - delegation might be preferable if the interface of the derived S classes is not uniform. (2) Then we are back to the OP current implementation - one benefit of making Xacc a template and moving the work method into Xacc is that the work method can have a specialized implementation for each class S without exposing any of X internals to the rest of S. – Come Raczy May 10 '16 at 21:57
  • Very interesting solution that is even more restrictive regarding acces to X than the requirement. Classes of S have a uniform interface: a few member functions repeated in every classes. All of them inherits from an abstract class SBase, I think I can make Xacc template function inherit from SBase and a call to virtual SBase::work() should work, yes ? – shrike May 10 '16 at 22:55
  • 1
    @shrike yes, Xacc can inherit from an SBase class where work would be a (pure) virtual method. – Come Raczy May 10 '16 at 23:57
  • @Come: working on your solution, I'm trying to make template Xacc generic to any class X, Y, ... so I added a template parameter this way: `template Xacc {...}`, and changed the friendship in `X` this way: `template friend class Xacc;`. How can I restrict friendship to partially specialized `Xacc` ? – shrike May 11 '16 at 09:06
  • 1
    @shrike unfortunately, the standard explicitly prevents this in 14.5.4 [temp.friend]: Friend declarations shall not declare partial specializations. – Come Raczy May 11 '16 at 16:24
1

Perhaps you could use some variant of the passkey pattern

class XKey {
    XKey(){}
    friend class Xacc;
};

class X {
public: 
    int& get_prot(XKey) { return prot; } // Only accessible to those who can construct XKey
protected:
    int prot;
};

class Xacc {
protected:
    Xacc(X& x) : x(x) {}
    X& x;    
    XKey getKey() { return XKey(); }    
};

class A : public Xacc {
public:
    A(X& x) : Xacc(x) {}
    void work()  { x.get_prot(getKey()) = 1; }
};

int main() {
    X x;
    A a(x);
    a.work();
    //x.get_prot(XKey()) = 2;  // Error: XKey::XKey() is private   
}
Community
  • 1
  • 1
Chris Drew
  • 14,926
  • 3
  • 34
  • 54
  • That's a very interesting solution, that suits the needs, thanks. AFAIU, I could even use this idiom on a container Xcnt owning the X instance as private member and add a member function: `X& Xcnt::getx(XKey);`, with X members all public; this way no need to add extra accessors to X, am I right ? – shrike May 10 '16 at 22:39
  • 1
    @shrike That would probably work, yes. You would have to be careful that `X` doesn't leak. – Chris Drew May 11 '16 at 05:06
0

I think the solution is: you have to have a base class for your set S.

S can be your class X or a separated (new) class from X

software project is balance of requirement and design, when you listed all 6 requirements, the solution is limited to you have to have a base class Xacc which has common functionality: access your X

do you know story "castles in the air"? how do I only have 2nd floor without 1st floor?

0xFFFFFFFF
  • 852
  • 5
  • 9
  • 1
    A base class `Xacc` just like the one in the current implementation I provided in example ? I am trying to find a better solution than that, this is the reason of my question. – shrike May 10 '16 at 15:35
  • you even do not need `Xacc`, as what I said, all your class `A` `B` `C`... can inherit just from `X`. – 0xFFFFFFFF May 10 '16 at 15:40
  • 1
    "Creation and destruction of instance s must keep instance x alive and unaltered." If A, B, C inherit from X, each time I will create/destroy an instance of them, I also create/destroy what I need to keep alive. – shrike May 10 '16 at 16:37