First time here? Check out the Help page!
1 | initial version |
If you really are interested in the reason behind this implementation, I'll try to explain exactly why. After all, the use of OptionalXXX classes is paramount in properly using the openstudio SDK via any bindings, so it might be a good idea to really understand why.
The OpenStudio SDK is written in C++. When for example you use an OpenStudio Measure, that is written in Ruby, you use for example the Ruby bindings. OpenStudio makes use of something called SWIG - Simplified Wrapper and Interface Generator - to expose this C++ SDK to a variety of programming languages such as C#, Ruby, (Java, Python, etc...). SWIG basically writes (almost) automatically "spaghetti code" to make the link between Ruby and the calls to the C++ library.
C++ is strongly typed, and in particular it cannot return different types from a single function. One typical thing you can encounter is when you want a function to return either an object, or "Nothing" when it doesn't exists. Consider this Ruby function that is perfectly valid in Ruby:
def coolingCoil(terminal)
# Assumes that the _coilNumber attribute stores a unique identifier...
# And allCoils is an Array of all the coils in the model
if terminal._coilNumber > 0:
return allCoils[terminal._coilNumber]
else:
return nil # This returns nil, we could even have more return types given different conditions
# Usage
coil = coolingCoil(terminal)
if not coil.nil?
# We can assume that our "coil" variable is an Object of type "Coil" now
coil.callAMethodOnCoilObjects()
end
The above is not possible in boilerplate C++. Enters the concept of boost::optional
(doc) which now made it into the Standard Template Library (STL) starting at C++ 17 as std::optional
(doc).
The class template std::optional manages an optional contained value, i.e. a value that may or may not be present.
A common use case for optional is the return value of a function that may fail. As opposed to other approaches, such as std::pair<t,bool>, optional handles expensive-to-construct objects well and is more readable, as the intent is expressed explicitly.
So the above ruby example looks kinda like this in C++:
boost::optional<Coil> coolingCoil(Terminal& terminal) {
boost::optional<Coil> result;
if (terminal._coilNumber > 0) {
result = allCoils[terminal._coilNumber];
} // Else result is still uninitialized
return result;
}
# Usage
boost::optional<Coil> _coil = coolingCoil(terminal);
if (!_coil.empty()) {
Coil coil = _coil.get();
coil.callAMethodOnCoilObjects();
// (or just _coil->callAMethodOnCoilObjects()... but let's not complicate things)
}
Now OpenStudio's SDK is usually pretty darn logical with return types as a result. It's also pretty logical with the method names (Well...part is because of C++ strongly typed characteristics, but mostly it's because there was a lot of thoughts into it). If you know enough of the underlying object, you can infer the return type at pretty much all times. Here are a couple examples:
terminal.coolingCoil()
returns a boost::optional<XXX>
(where XXX
is HVACComponent
or one of its children)terminal.coolingCoil()
would return a XXX
directly.Space
can have zero or one ThermalZone
: space.thermalZone()
returns a boost::optional<thermalzone>zone.spaces()
(hint: it's called spaces()
not space()
...) returns an array of spaces (std::vector<Space>
in C++).Hope that helps.