I have a very specific need to access functionality specific to a derived class of which I get an instance in a unique_ptr
at construction of the containing class. This containing class must then move the unique_ptr
upcast to its base class to the containing class' base class constructor, where it is finally move
d for ownership in that base containing class. Some code should help:
class MemberBase {};
class MemberDerived : public MemberBase { /*some public stuff not in MemberBase*/ };
class MainBase {
std::unique_ptr<MemberBase> member_;
public:
MainBase(std::unique_ptr<MemberBase> member) : member_(std::move(member)) {}
};
class MainDerived : public MainBase {
MemberDerived* member_derived_;
// This class proceeds to use MemberDerived-only functions
public:
// How to write the initialization list below?
MainDerived(std::unique_ptr<MemberDerived> member)
: MainBase(std::move(member)), member_derived_(member.get() /*nullptr!!*/) {}
};
Ignoring the question of overall class design (I'm aware that the coupling to MemberDerived
-specific functions in MainDerived
is not ideal; this is inherited code that cannot be altered without major refactor), how can I snag the raw pointer before forwarding the unique_ptr
to MainBase
?
Find below some ideas I have thought of and why I do not think they are great.
- Downcast protected accessor:
// Add this method to the protected section of MainBase:
MemberBase* MainBase::get_member() { return member_.get(); }
// Then downcast in MainDerived's c'tor
MainDerived::MainDerived(std::unique_ptr<MemberDerived> member)
: MainBase(std::move(member)), member_derived_(dynamic_cast<MemberDerived*>(get_member())) {}
This should work but uses dynamic_cast
(major downside in and of itself) and of course if someone changes the type passed to the c'tor to something not derived from MemberDerived
it will break with no help from the compiler.
- Pass in the pointer twice:
// member and member_derived must point to the same object!
MainDerived::MainDerived(std::unique_ptr<MemberDerived> member, MemberDerived* member_derived)
: MainBase(std::move(member)), member_derived_(member_derived) {}
Besides making for a pretty ugly c'tor signature it would be pretty easy for the user to pass a different pointer for both arguments or do the move
before calling get
. Furthermore the user is now forced to create a local variable to pass it into both places. Did I mention it's ugly?
- Flip initialization order with helper function trickery:
template <typename T>
std::unique_ptr<T> ExtractPointer(std::unique_ptr<T> p, T** target) {
*target = p.get();
return std::move(p);
}
MainDerived::MainDerived(std::unique_ptr<MemberDerived> member)
: MainBase(ExtractPointer(std::move(member), &member_derived_)) {}
Now I was actually a bit surprised that this did not produce any warnings/errors (gcc 5.4.0 with -Wall
). Part of me likes this as it seems safe in the sense that it is hard to break but the circuitous initialization of member_derived_
gives me minor chills.