Reputation: 258618
I'm dealing with something that goes along these lines:
I have a class that is quite complex and a member that depends on some stuff that isn't set when the class is initialized, or is set on the go. Objects of that class make sense even when that member isn't set. It can also be reset depending on other changes that are made to other members.
Now, assume this "special" member is computationally-expensive to set, and so I'm delaying computing it on request.
So:
class Class
{
X x;
Y y;
SpecialClass specialObject;
public:
void setX(const X& newX);
void setY(const Y& newY);
//----
SpecialClass getSpecialObject() /*const*/
{
computeSpecialObject();
return specialObject();
}
private:
void computeSpecialObject()
{
//specialObject depends on x and y
//and is expensive to compute
//this method is a bottleneck
}
};
I don't want to call the compute
method every time I change x
or y
because it's expensive, so I'm left with the dilemma:
const
? Logically, the getter should be const
, but it can't. There's also the downside that it can't be called on const
objects.specialObject
mutable
, but it doesn't seem like the right thing to do.computeSpecialObject
before the get? - what if someone forgets? They'll get an out-dated result. Is there a design pattern that deals with this? A good approach? Or is the class design just wrong? (I'd lean towards this last one, but changing the class isn't really an option)
Note: I've made the member mutable
, want to know if there's a better solution.
Upvotes: 4
Views: 380
Reputation: 24133
There are two directions you could go, more OOP
or more Functional
. One involves caring less about state manipulation, but rather behaviour, the other completely forgets about behaviour, and cares about returned state.
For me a key OOP
principle is Tell, Don't Ask
, or write no getters or setters
.
Design your objects to be told what to do, to be autonomous. Don't ask it to return some object which you can then use to do something. Just tell it to do the thing you want in the first place. If you're telling an object to do something then you likely expect it to change state, and it's not right for it to be const
.
Your SpecialClass
may provide some service doService()
. You can instead tell Class
to doSpecialService()
, which is rightly mutable.
An alternative is for the creation of this object to use some other object to do the creation. So a function can be const but take a non const parameter:
class Class {
public:
void doService(ServiceProvider& serviceProvider) const {
serviceProvider.doService(x, y);
}
};
With this you would pass in a SpecialServiceProvider&
which would create the correct SpecialClass
for the given X
and Y
. It would be mutable. It would seem correct to modify state in the provision of the service. Maybe you could have a map caching SpecialClass
objects for (X
, Y
) pairs.
The other direction is to make your objects immutable. Whenever you want some new state create it using the old state as a basis. This could have a knock on effect until you have turtles (almost) all the way down:
class SpecialBuilder {
public:
SpecialBuilder withX(const X& newX) const;
SpecialBuilder withY(const Y& newY) const;
SpecialClass build() const;
};
SpecialBuilder specialBuilder;
SpecialClass special = specialBuilder.withX(x).withY(y).build();
You can share data between each returned SpecialBuilder
as it is immutable.
Upvotes: 0
Reputation: 20320
It's always a fine line this one, not calling it when you don't need to, versus introduces holes in caller, that mean it might not have been and returning incorrect results.
Me I'd move compute to be method of special object and the treat this class as a wrapper for the arguments for the compute method that class. A bonus ball is you can unit test the computation.
Then it's just a question of deciding when you need to call SpecialObject.Compute(x,y) again or simply return the last result. Another thing I might look at if I could, would be if X has changed but Y hasn't can I simplify the calculation. i.e. keep some intermediate results.
Not sure how applicable it is for you, but one of the things I regularly end up doing is injecting a something that does the compute, so I tend to fall into this pattern by default.
Upvotes: 0
Reputation: 126462
I can make specialObject
mutable
, but it doesn't seem like the right thing to do.
Why so? That's exactly why mutable
exists: to allow a const
function to be logically const
without the need to physically leave the object unchanged (and if you make the object mutable
, remember about ensuring thread-safety - I'm sure you know what I mean).
This is true as long as the initialization of the SpecialClass
object is not something that alters the logical state of the object, of course, because that's what const
promises not to do.
In that case, the function itself is simply not const
in nature, and it should likely be named something different than just getSpecialObject()
: computeAndReturnSpecialObject()
could be a candidate.
Upvotes: 4
Reputation: 10613
I'd leave the const
and either make specialObject
mutable
or keep a pointer to the specialObject
instead of just 'embedding' it into the class.
I would also add a bool dirty
flag that is mutable
and set it whenever a change is made that invalidates the computation. I would then check the flag inside computeSpecialObject
and do the work only if it's set. Using a pointer, you could even delete
the old computation object whenever a change invalidates an existing computation, but that opens a whole 'nother can of worms.
Or am I missing something?
Upvotes: 3