I have something similar to the code below, where a group of classes with similar shared behaviours (Tool1, Tool2), all inherit from an abstract class (ITool). All these classes own their own optional of a corresponding class (Tool1Attachment, Tool2Attachment), which also inherit from an abstract class (IAttachment).
class ITool {
private:
virtual ? DoGetAttachment() = 0;
// Other shared behaviours...
public:
? GetAttachment() {return DoGetAttachment();}
// Other shared behaviours...
};
class Tool1: public ITool {
std::optional<Tool1Attachment> opt_attachment;
? DoGetAttachment() override;
public:
[...]
};
class Tool2: public ITool {
std::optional<Tool2Attachment> opt_attachment;
? DoGetAttachment() override;
public:
[...]
};
// --------------------------------
class IAttachment {
[...]
};
class Tool1Attachment : public IAttachment {
[...]
};
class Tool2Attachment : public IAttachment {
[...]
};
It makes sense for the tool class to have an optional - in context, it may or may not have an actual instance at any given time.
An issue arises if I have an IAttachment, and would like to get the IAttachment.
I originally used pointers for this, i.e. IAttachment* GetAttachment();
, which worked "fine". However, it seems to lose some of the more explicit checking of an absent value that optional provides, making it easier to run into nullptr issues in the future.
I also tried different constructs (std::reference_wrapper) with optionals but kept running into invalid covariant errors.
Is there a way of doing this that allows the return of an optional (or similar construct)? What should the return types look like? Or are the pointers with nullptr checks most suitable here?
? GetAttachment() {return DoGetAttachment();}
The answer really boils down to what you expect of the
DoGetAttachment
function. Should it "forward the optionalness" of the member to the caller, together with the responsibility of checking if the value exists? Or should theDoGetAttachment
function return a reference if the storedstd::optional
has a value and throw an exception if it doesn't?Assuming you want to "forward the optionalness" to the caller of the function, you basically have two options.
Option 1: Use (raw) pointers
Use pointers, as you already have done according to your question. It is the most simple solution and standard practice.
Option 2: Use
std::optional
together withstd::reference_wrapper
std::optional
is not polymorphic, so you cannot cast astd::optional<Derived>
to anstd::optional<Base>
. What you can do is return anstd::optional<Base*>
or anstd::optional<std::reference_wrapper<Base>>
. IMHO, I would preferstd::reference_wrapper
, because otherwise you have the "optionalness" property on two levels: Once for thestd::optional
and once for the stored pointer. So you need to check twice for existence.Option 2 introduces some additional complexity to your code and it is up to you to decide if it is worth it.
Arguments for using
std::optional
are, thatstd::optional
allows you to use the monadic operators ofstd::optional
, such asvalue_or
,and_then
,transform
(some of which are C++23 features); and thatstd::optional
s do not perform any dynamic memory allocation (if that is something that you need).Here is an implementation, where
DoGetAttachment
returnsstd::optional<std::reference_wrapper<IToolAttachment>>
.Output:
Live Code Demo