Best practice for a class member that is the product of 2 other class members

Hi, let's say I have a class that uses a rectangle's area, but the user cannot set the area as a setter, it needs to be calculated internally from a height and width setter.

As a general best practice, which one of the options below would be best to use (or any other approach I did not think of?):

1. a single set function for the height and width, and the area is the only private fct

1
2
3
4
5
6
7
8
 class rect {
  private:
  _area;
  public:
  void setDimensions(int h, int w) {
    _area = w*h;
  }
}



2. two set functions for the height and width, and the area is constantly calculated every time it needs to be used. (wastes cpu, but the set fcts seem cleaner, and there is no redundant private member)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 class rect {
  private:
  _w;
  _h;
  public:
  void setHeight(int h) {
    _h = h;
  }
  void setWidth(int w) {
    _w = w;
  }
  int calculateArea() {
    return _w*_h
  }
}



3. two set functions for the height and width, and the area saved as a 3rd private member. (creates a redundant private member, but at least the calculations are only done once during set fcts)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 class rect {
  private:
  _w;
  _h;
  _area;
  public:
  void setHeight(int h) {
    _h = h;
    _area = _w*_h;
  }
  void setWidth(int w) {
    _w = w;
    _area = _w*_h;
  }
}


Thanks a lot!!
I tend to do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>

struct rectangle
{
    constexpr rectangle( double height, double width ) : height_(height), width_(width)
    { if( !valid() ) throw std::invalid_argument("bad value for rectangle height/width") ; }

    constexpr double height() const noexcept { return height_ ; }
    void height( double new_height )
    { if( !new_height < 0 ) throw std::invalid_argument("bad value for rectangle height") ; height_ = new_height ; }

    constexpr double width() const noexcept { return width_ ; }
    void width( double new_width )
    { if( !new_width < 0 ) throw std::invalid_argument("bad value for rectangle width") ; width_ = new_width ; }

    constexpr double area() const noexcept { return height() * width() ; }

    private:
        double height_ ;
        double width_ ;
        constexpr bool valid() const { return height_ >= 0 && width_ >= 0 ; }
};

int main ()
{
    rectangle rect( 1.2, 3.4 ) ;
    std::cout << "area: " << rect.area() << '\n' ;

    rect = { 5.6, 7.8 } ; // change both height and width
    std::cout << "area: " << rect.area() << '\n' ;

    rect.height( 9.0 ) ; // change height
}
Firstly, objects don't have setters/getters, they have methods that match operations you'd do on the object. I think that nonsense started with visual basic, in any event, if you find yourself thinking in these terms, stop.

As for what to do about calculated values, it depends on how costly the operation is. If you calculate an area once, then the object can do the calculation in the method. If you call it lots of times, you may want to cache it in a member.

This is a related video that discusses this, and uses calculating Fibonacci numbers as an example.
https://www.youtube.com/watch?v=OQ5jsbhAv_M
L10, L14 - ! ??

As rectangle is using exceptions, wouldn't main() have a try/catch clause for invalid _argument?

@jcsb1944. IMO it depends upon the usage. If area() is called a lot after values have been set, then it may make sense to maintain a calculated value. Otherwise for the constructor/setter I'd go with JLBorges as above. For getter, I'd overload height() and width(). Note that with this you can't create an object with a default constructor - the constructor must be supplied values. If you need a default constructor, then you need to decide what values should be used for height/width (0?).
Last edited on
> As rectangle is using exceptions, wouldn't main() have a try/catch clause for invalid _argument?

In this case, we know that we are passing values which are known at compile time;
we know that an exception would never be thrown.


> If area() is called a lot after values have been set, then it may make sense to maintain a calculated value.

Multiplying two scalars and returning the result in an inline function is a trivial operation; it hardly takes any time. The memory overhead of caching is probably just not worth it in this particular case.
Topic archived. No new replies allowed.