First off lets be clear on the paradigm.
- Data Structures -> a layout of memory that can be traversed and manipulated by suitably knowledgeable functions.
- Objects -> a self contained module that hides its implementation and provides an interface that may be communicated through.
Where is a getter/setter useful?
Are getters/Setters useful in Data Structures? No.
A Data Structure is a memory layout specification that is common to and manipulated by a family of functions.
Generally any old new function can come along and manipulate a data-structure, if it does so in a way that the other functions can still understand it, then the function joins the family. Otherwise its a rogue function and a source of bugs.
Don't get me wrong there could be several families of functions warring over that data-structure with snitches, turn-coats, and double-agents everywhere. Its fine when they each have their own data-structure to play with, but when they share it... just imagine several crime families disagreeing over politics, it can become a mess really fast.
Given the mess extended function families can achieve, is there a way to encode the data structure so that rogue functions don't mess everything up? Yes, they are called Objects.
Are getters/setters useful in Objects? No.
The whole point of wrapping a data structure up in an object was to ensure that no rogue functions could exist. If the function wanted to join the family, it had to be thoroughly vetted first and then become part of the object.
The point/purpose of a getter and a setter is to allow functions outside of the object to alter the memory layout of the object directly. That sounds like an open door to permit in rogues...
The Edge Case
There are two situations were a public getter/setter make sense.
- A portion of the data-structure within the object is managed by the object, but not controlled by the object.
- An interface describing a high-level abstraction of a data-structure where some elements are expected to not be in control of the implementing object.
Containers and container interfaces are perfect examples of both of these two situations. The container manages the data-structures (linked-list, map, tree) internally but hands control over the specific element to all and sundry. The interface abstracts this and ignores the implementation entirely and describes just the expectations.
Unfortunately many implementations get this wrong and define the interface of these sorts of objects to give direct access to the actual object. Something like:
typedef ...T... TRef; //<somehow make TRef to be a reference or pointer to the memory location of T
TRef item(int index);
This is broken. The implementations of Container must explicitly hand control of their internals over to whomever uses them. I've yet to see a mutable-value language where this is fine (languages with immutable-value semantics are by definition fine from a data-corruption perspective, but not necessarily from a data-spying perspective).
You can improve/correct the getters/setter by using only copy-semantics, or by using a proxy:
operator T(); //<returns a copy
... operator ->(); //<permits a function call to be forwarded to an element
Proxy<T> operator=(T); //< permits the specific element to be replaced/assigned by another T.
Proxy<T> item(int index);
T item(int index); //<When T is a copy of the original value.
void item(int index, T new_value); //<where new_value is used to replace the old value
Arguably a rogue function could still play mayhem here (with enough effort most things are possible), but the copy-semantics and/or proxy reduces the chance for a number of errors.
- interactions with the sub element are type-checked/type-checkable (in type lose languages this is a boon)
- The actual element may or may not be memory resident.
This is the last bastion of getters and setters working on the type directly. In fact I wouldn't even call these getters and setters but accessors and manipulators.
In this context sometimes manipulating a specific portion of the data-structure always/almost-always/generally requires specific book keeping to occur. Say when you update the root of a tree the look-aside cache needs to be purged, or when you access the external data element, a lock needs to be gained/released. In these cases it make sense to apply the DRY principal and parcel those actions together.
Within the private context, its still possible for the other functions in the family to side-step these 'getters and setters' and manipulate the data structure. Hence why I think of them more as accessors and manipulators. You could access the data directly, or rely on another family member to get that part right.
In a protected context, its not terribly different to a public context. Foreign possibly rogue functions want access to the data-structure. So no, if they exist they operate like public getters/setters.