3

I have a class Node. This class can add or remove other nodes relative to itself. Node is used by a List class. To prevent the nodes being modified directly (externally, IE not by the appropriate classes) during usage and causing problems with the List class, the nodes add/remove functions are either protected or private. This requires that List class is a friend to Node.

However, the problem with this is that the List class itself is a template class for other subclasses, and adding prototyping/adding the friend keyword for each subclass is clearly not the best solution.

How would I design the Node and List class/subclasses so that:

  • Node cannot be constructed by itself externally, is only constructed with specific classes/subclasses?
  • Node can construct/remove other nodes given above?
  • Node functions are only accessible to specific classes (List, list subclasses, and list helper classes - list helper classes are not subclasses of list)?
  • The node variable (Item) is publicly accessible give above?
  • List, list subclasses and list helper classes can directly modify or indirectly modify the non-public variables of Node?

Are these possible, and if so, how?

SE Does Not Like Dissent
  • 1,767
  • 3
  • 16
  • 36
  • Why would external code to the `list` have access to the `node`s at all? The nodes are an internal detail of how the list is implemented, and that should not leak outside of the list itself. For iterating over the list, you can provide a `iterator` type that has access (but is under your control) and that will only offer access and increment operations... quite similar to what the STL lists do... again, take a look at the code there. – David Rodríguez - dribeas Sep 10 '11 at 12:34

4 Answers4

6

The standard library’s std::list class template is an example of how to design a list class so that no access to the internal nodes etc. is given to the client code.

With no access, no undesired meddling…

However, in general, it's more practical to trust a little, to not take it upon your shoulders to ensure that others’ code will be correct. It is a lot of work to express all the usage restrictions in C++. Opening up just a little can save a lot of work – so it’s a trade-off.

Cheers & hth.,

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • 3
    +1 for trusting the client to not be stupid. Because this is C++. And that's how we roll. – Seth Carnegie Sep 10 '11 at 11:58
  • I am more concerned about security rather than ignorance. – SE Does Not Like Dissent Sep 10 '11 at 12:03
  • @SSight3: What exactly is that *security* that you mention in the comment? If you are talking *security* as in intrusion protection, then you might want to rethink, *private* or *protected* will not block users that have access to the code from anything at all... – David Rodríguez - dribeas Sep 10 '11 at 12:30
  • @David: Good point. I suppose I just want to be absolutely sure that the node class cannot be modified incorrectly in anyway given that is an assumption list (and it's subclasses) rely upon. – SE Does Not Like Dissent Sep 10 '11 at 12:48
  • @SSight3: The next question that comes to mind is *what are the types derived from `list`*? Are they *different* lists, or just *users* of a particular list? If they do not modify the implementation, but rather *use* the base `list`, then all the invariants should be kept at the `list`, level, and the derived types should not even access the nodes (Also consider that inheritance is for code reuse only in as much as allowing older code that used the base to also use the derived --not the other way around, i.e. it is not for derived classes to *reuse* the base implementation) – David Rodríguez - dribeas Sep 10 '11 at 13:00
  • @David: The subclasses clarify (and where applicable) expand the templatelist into other specific roles, like a charlist (so it can read NULL terminated strings, handle std::string, append nulls etc). The helper classes are basically iterators for templatelist (including a read-only iterator for const applications). Templatelist has it's own iterator, but it can't be used during const calls. – SE Does Not Like Dissent Sep 10 '11 at 13:18
  • @SSight3: So you have a list, and a list of chars that is basically a list but nul terminated, are they to be used polymorphically? Do you want to pass a `charlist` to a function that takes a `list` and expect it to work as a regular list? A string can be implemented in terms of a vector (underlying type to manage the data), but that does not make a string a vector. Is it true that for every operation that processes a `list` that operation can be freely applied to a `charlist`? – David Rodríguez - dribeas Sep 10 '11 at 15:47
2

I would make Node a protected nested class of List:

class List
{
    ...
    protected:
        class Node
        {
            ...
        };
};

This way, only List and its subclasses can access it. Since it is nested within List, list may access its private/protected members and functions. It also helps to highlight the functional relationship between the two classes. This probably takes care of all your dot points except the third.

EDIT double checking my facts, it seems that in C++ enclosing classes do not have special access permissions to nested class members after all (seems that's a Java thing), see here. As such, you will need to make Node members public, but I still think this solution encourages good encapsulation.

Mac
  • 14,615
  • 9
  • 62
  • 80
  • I had thought of this, but I had to put it aside as list makes use of helper classes. I'd probably have to re-write the helper class to somehow be a subclass of list itself. Wouldn't this make node accessible via List::Node though? – SE Does Not Like Dissent Sep 10 '11 at 11:58
  • @SSight3: This will make `Node` accessible via `List::Node` only to *friends and relatives* (i.e. `friend` classes and deriving types). I am not sure how you are managing the helper classes, or what they are for (i.e. you did not provide nearly enough information in the question), but you can probably continue to do the same, or declare them as friends (I would avoid forcing inheritance for no reason) – David Rodríguez - dribeas Sep 10 '11 at 12:32
1

I am not sure that I understand the problem in question, so instead of providing an answer I will provide with different approaches:

  • Have list be the only friend of node, and ensure that it offers all the operations that types deriving from list will need as protected methods. In this approach, the list type serves as a proxy to the node class for all of is derived types.

  • Have node be a protected internal type to list. The fact that it is internal to list and it is protected locks everyone outside of the hierarchy of list from using the type at all, all of the methods in node can be public.

  • Be friendly and trusty: leave the design as it is, and make some of the operations in node public to all. Trusting the users with the contents and document the invariants on which your list class is built so that user code will know what operations not to perform.

I prefer the first option, as it provides the list with the responsibility of managing the nodes, which is a good thing, at least better that sharing responsibilities... there is a single point where things can go wrong: the list class, and it is up to that class to maintain its own invariants.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
0

As an idea, using Mac's answer:

Declare a class called NodeAccess, NodeAccess contains the Node class declared under protected (like Mac's answer).

Declare the functions in node as public.

class NodeAccess
{
    protected:
        class Node
        {
            public:
            void Function(){}
        };
};

Then for every class that wants access to the node, they inherit NodeAccess as protected, which grants them and all other subclasses access rights to node within the protected ruleset, but prevents any other class from accessing node directly.

class Helper: protected NodeAccess
{

};

class OtherHelper: protected Helper
{

};

//etc

SE Does Not Like Dissent
  • 1,767
  • 3
  • 16
  • 36