6

I struggle with this question very often and couldn't find any clear solution. I think I know the motivation of getters/setters.

Prior Information:

When realizing real life data, usually the data is encapsulated in more than one layers. For example:

// 1st stage data types ------------------------------
struct Cartesian
{
    int32_t x;
    int32_t y;
    int32_t z;
}

struct GeoLocation
{
    double_t latitude;
    double_t longitude;
    int32_t altitude;
}

// 2nd stage data types ------------------------------
struct Drone
{
    Cartesian baseOffset; // m
    Cartesian velocity; // m/s
}

struct Plane
{
    GeoLocation location; // semicircle
    Cartesian velocity; // knots
}

// 3rd stage data types ------------------------------
struct Swarm
{
    Plane base;
    Drone member[10];
}

In C++, I use classes instead of structs (because why not?). And the problem arises when a data about Swarm[3].member[8].velocity.x is received over some communication channel. Realize that there can be more than one swarm in a system.

Requirement:

By MISRA C++ rules, a function cannot return non-const reference to any class-member because the member should not be changed without that class' permission/knowledge.

Question:

When I use getters and setters, I cannot say "Swarm[3].member[8].velocity.x"; instead I may say this in several ways:

1. This is not allowed as get() functions returns const reference and cannot call set().

Swarm[3].getMember(8).getVelocity().setX(5); (or set("X", 5))

2. This method carries all burden into the Swarm class. Although the code seems shorter for who is calling Swarm class, it is very heavy to code and to do maintenance in the background in case of a change.

Swarm[3].setMemberVelocity(8,X,5)

3. This method is somewhat in between, but here the problem is you might be sacrificing efficiency because every time a new data arrives first you create a temporary variable, get it, fill it and set it.

Cartesian tempVelocity = Swarm[3].getMember(8).getVelocity();

tempVelocity.x = 5;

Swarm[3].setMemberVelocity(8, tempVelocity);

Which one of these 3 methods are the best? Or are there any alternatives that I might use?

Thanks in advance.

bomberman
  • 142
  • 10
  • Why is setX(5) not allowed but setVelocity is? – Sorin Oct 05 '18 at 08:46
  • 3
    Getter/setter pairs are just a way of pretending that you don't have public members. They're largely pointless and only clutter the interface. I suggest alternative 4: don't use getters and setters. – molbdnilo Oct 05 '18 at 08:58
  • 1
    "In C++, I use classes instead of structs" - In C++ classes and structs are exactly the same thing, they only differ with default access specifier (`class` members are by default `private`, `struct` members are by default `public`) – Yksisarvinen Oct 05 '18 at 08:58
  • 2
    And as Uncle Bob said, getters and setters are a lie. You pretend to use encapsulation, but everything is public anyway. Just make your members public to make your intentions clear (or better rethink your design). – Yksisarvinen Oct 05 '18 at 09:01
  • 1
    I think the MISRA statement you are referring to says about situations that may get an object into the inconsistent state. Thus, as long as your `Swarm` class may be easily replaced by `typedef`, it's not a reason to apply that rule. – Andrii Oct 05 '18 at 09:45
  • @Sorin You cannot change a const variable (get returns const) – bomberman Oct 05 '18 at 10:30
  • @Yksisarvinen Maybe I should have written it more clear. I use classes normally, not structs, and use get and set functions as public only. So, everything is private actually. (just writed down in this way not to write all those constructor, destructor, public, private etc. Laziness..) – bomberman Oct 05 '18 at 10:34
  • @ÖöTiib Can you clarify more? How may I not use hierarchy here? – bomberman Oct 05 '18 at 10:39
  • @molbdnilo I agree up some extent. The point is what if the data on channel says altitude is 100000 meters but you are not building a rocket. (So you add an extra check that if it is in range when setting) – bomberman Oct 05 '18 at 10:43
  • 2
    @ozercik This is exactly what "getters and setters are a lie" mean. Your data members are not private, they are public. You pretend they are private by declaring them `private`, and then add 2 methods to make them public. If you have trivial getter and setter, make data member public instead. Another point is that you want to create a [train wreck](http://wiki.c2.com/?TrainWreck). Let's say I'm using `Swarm` class somewhere. What's the use-case for setting one direction of velocity of one of drones in my `Swarm`? Can't it be managed by `Swarm` class? Can't I just pass a `Cartesian` instead? – Yksisarvinen Oct 05 '18 at 11:16
  • 1
    @ozerik use class if the class has an invariant; use struct if the data members can vary independently. – Öö Tiib Oct 05 '18 at 11:24
  • @ÖöTiib Using structs might deceive my static analysis tool for MISRA but in heart I know that classes and structs are the same with just one catch, so I try to apply the rule and use classes as well. Maybe what you are saying is the reason that MISRA left structs more unrestricted, who knows? – bomberman Oct 05 '18 at 11:54
  • @Yksisarvinen as I said to 'molbdnilo' that is true, but consider setter to be a non-trivial one. As of other issue, I would love to not create a train wreck. And the use case is that handling of arrival of message having related data which continues with some other irrelevant job. – bomberman Oct 05 '18 at 12:06
  • @ozercik lot of MISRA is about choice between things that are same. – Öö Tiib Oct 05 '18 at 13:02
  • @ÖöTiib Do you mean MISRA prefers structs over classes? – bomberman Oct 05 '18 at 13:41
  • @ozercik MISRA coding standard limits in what situation we type "class" and in what situation we type "struct". We either violate it or follow it, can't do both. – Öö Tiib Oct 05 '18 at 15:39
  • @ozercik Why you need to use class, getter and setter ? In C++, using struct is just a convention for Plain Old Data types, and use classes for everything else. Which rule of MISRA states that PODs must be class and encapsulated ? I can't find it. – Hiroki Oct 05 '18 at 16:01
  • @Hiroki No rule in MISRA states that. But knowing struct and class is the same, (as discussed on previous comments) why did MISRA do such a distinction? – bomberman Oct 05 '18 at 16:18
  • @ÖöTiib I may have not understood standards correctly but I only see rules to apply after I choose what to use. If there is such a suggestion of structs vs classes I will be more happier than you can imagine. :) – bomberman Oct 05 '18 at 16:25
  • @ozercik I understand. I can only say Good Luck. BTW, the MISRA document was very educational for me. Thank you for giving me an opportunity for broadening my view. – Hiroki Oct 05 '18 at 16:34
  • @ÖöTiib I have read the MISRA document several times more today having in mind that what you have said. I saw that it differentiates structs as POD type and does not behave same to classes after connecting several rules and example code (11-0-1, example in 0-2-1, etc.) Although it still seems stange to me, it is acceptable. If you can express these more clearly as an answer and in a structured way, newcomers will benefit more. Thank you. – bomberman Oct 09 '18 at 08:49

3 Answers3

2

I may not know MISRA to a full extent. However, it seems like it is counter-productive to a flexible design (and not at all c++ish). Suppose I find out I need:

struct SuperSwarm {
  Swarm swarms[10];
};

Then following your option 2, I would need to implement a host of setters/getters for all the inner aggregates, since it sounds somewhat like in your case you need to be able to set all data individually. The same thing happends if you need to change something in Drone then everything needs to be updated. For a good and flexible design you can see how this is a nightmare really. There is not really a good alternative abiding by the rules you set out. That is why you usually return a non-const reference and use that if you don't need special handling. That, in some ways, future-proofs your code as opposed to just a public member variable.

One way you could 'cheat' (and use your option 3) and still be flexible design-wise is to proxy everything - like for example a std::shared_ptr. When you return a std::shared_ptr it seems like it is a copy and it is - but its actually just a proxy to the same pointed to object (which is similiar to how things work under the hood in some other OO programming languages). You may need a more appropriate proxy class though. But then again why bother ? why not just use structs and say it is a data structure and not some class holding (another) responsibility. After all, then you would express your intent clearly.

darune
  • 10,480
  • 2
  • 24
  • 62
  • Pointers are actually makes variables *non-class-data* and allows to change the non-const pointed data. I sometimes use this method, but as you mentioned I feel like cheating. And also, why to handle pointer destruction as compared to do-nothing approach, right? I really think that this rule limits the way you design your program. On the other hand I think of that there is a valid reason behind the rule. I want to know if should I break it or there is a method that the designers wanted me to make use of it. – bomberman Oct 05 '18 at 13:08
  • 1
    Not being allowed to actually expose some inner parts directly will inherently call for extra manipulaters and a cumbersome design as outlined in my post. However, this may be ok or even the preferred method for some library API or some class that can actually benefit from this strong encapsulation/abstraction. For general work it is just a too cumbersome design with mostly drawbacks. Maybe you are not reading MISRA correctly as some users have suggested. – darune Oct 08 '18 at 13:17
1

If it arrives in bulk (as in a chunk for all the swarms), I would have methods/members that delegate chunks to subobjects. Say you just received a string update. You split out data for the first swarm and call swarm[0].update(chunk). That method would do it's own validation, then split the chunk down to info for each member and call update on the member with the smaller piece. Eventually you would get all the way down to Cartesian which will be able to update X and everything else.

Sorin
  • 11,863
  • 22
  • 26
  • The problem with that is my swarm is being dependent on chunk. Swarm should not change when the type of chunk itself changes. – bomberman Oct 05 '18 at 12:34
  • 1
    @ozercik If you trust other modules to update Swarm correctly, that would be reasonable. If you don't then Swarm need to update itself and will depend on the some type that encodes the update. – Sorin Oct 05 '18 at 13:55
1

There is clear need for plain old data, standard layout and trivially copyable. That kind of data makes it simpler to achieve that binary layout of it is portable and compatible with other programming languages. MISRA requires that kind of data to be declared using keyword struct.

As side effect such data makes it also possible to write code like:

swarms[swarmNo].drones[droneNo].velocity.x = 5;

That kind of deep chain navigation is still considered code smell and it has also name "train wreck pattern". However it at least does not have any unneeded get, set and () bloat to type.

All the rest of data should follow object oriented principles. There swarm itself has responsibility to do its maneuvers by commanding its drones and drone itself has responsibility to follow (or to refuse) such commands. Nothing should be able to simply set some individual property of something out of blue from outside.

MISRA requires that kind of data to be declared using keyword class. The non-static data members should be made private and non-const handles to these members should not be returned by public member functions.

Öö Tiib
  • 10,809
  • 25
  • 44