I have a strange situation with virtual classes and I need design help.
I have a solution, but I am looking for a better one. This is the code I've written that accomplishes what I am looking for, but it depends on pointers to pointers to pointers.
I feel like if I could just directly manipulate the underlying data pointer of the shared pointer, I could get away with one less layer of abstraction.
I would like to do this with one InterfaceWrapper, not two.
#include <stdio.h>
#include <memory>
#include <vector>
class Interface
{
public:
virtual void WriteIt() = 0;
virtual ~Interface() { }
};
class Foo : public Interface
{
void WriteIt() { printf ("Foo\n"); }
};
class Bar : public Interface
{
void WriteIt() { printf ("Bar\n"); }
};
// This class wraps Interface so we can call WriteIt on desctruction
// I'd like to do this in the Interface class, but you can't call virtual methods during destruction.
class InterfaceWrapper : public std::unique_ptr<Interface>
{
public:
InterfaceWrapper(Interface * i) : std::unique_ptr<Interface>(i) { }
~InterfaceWrapper() { (*this)->WriteIt(); }
};
// This class provides counted destruction to InterfaceWrapper
class InterfaceWrapper2
{
public:
InterfaceWrapper2 () : _ptr(new InterfaceWrapper(new Foo)) { }
void MakeBar() { _ptr->reset(new Bar); }
private:
std::shared_ptr<InterfaceWrapper> _ptr;
};
int main (void)
{
std::vector<InterfaceWrapper2> thing_vector;
// The default implementation will, print "Foo" on destruction.
InterfaceWrapper2 thing;
// That destructor should only happen once for all copies of 'thing'
thing_vector.push_back(thing);
// MakeBar changes the virtual implementation so it prints "Bar" instead of "Foo"
thing.MakeBar();
// When destructors happen, the program should just print "Bar" once.
return 0;
}
Anything is welcome, but I particularly interested in solutions which work on C++03 using boost (my example is C++11 but my 'real' code is C++03 using boost::shared_ptr).
I am basically looking for a better way to implement InterfaceWrapper2 in my example code. main()
is the best explanation of what I am trying to accomplish. Just remember the behavior is stuck in those virtual classes.
Is this what you're after?
Update 3
If you want to keep the Interface machinery, the following is a pretty succinct way to write the wrapper by just composing standard library features:
class InterfaceWrapper {
using UI = std::unique_ptr<Interface>;
std::shared_ptr<UI> _sui {new UI{new Foo}, [](UI*p){ (*p)->WriteIt(); delete p; }};
public:
void MakeBar() { _sui->reset(new Bar); }
};
See it Live On Coliru
Update 2
After realizing that an std::function<>
is already a dynamic, single-function, mutable interface that can be bound to any stateful functor, I thought of the following simplified version:
Live On Coliru
#include <memory>
#include <iostream>
#include <vector>
struct dynamic_release {
template <typename F> dynamic_release(F&& f) : _f(std::forward<F>(f)) { }
template <typename F> dynamic_release& operator=(F&& f)
{ _f = std::forward<F>(f); return *this; }
~dynamic_release() { _f(); }
private:
std::function<void()> _f;
};
void do_foo() { std::cout << "Foo\n"; }
void do_bar() { std::cout << "Bar\n"; }
int main(void) {
using InterfaceWrapper = std::shared_ptr<dynamic_release>;
using Thing = InterfaceWrapper::element_type;
{
std::vector<InterfaceWrapper> thing_vector;
auto thing = std::make_shared<Thing>(do_foo);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
} // prints "Foo" once
{
std::vector<InterfaceWrapper> thing_vector;
auto thing = std::make_shared<Thing>(do_foo);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
*thing = do_bar; // Prints nothing
thing_vector.push_back(thing);
} // prints "Bar" once
}
To enable optimizations in case you want to achieve more efficiency with state-less functors too, add a basic_dynamic_release
template that allows to use different functor types (like, e.g. void(*)()
):
Live On Coliru
#include <memory>
#include <iostream>
namespace detail {
template <typename InterfaceCallable>
struct basic_dynamic_release {
basic_dynamic_release() = default;
template <typename F> basic_dynamic_release(F&& f) : _f(std::forward<F>(f)) { }
template <typename F> basic_dynamic_release& operator=(F&& f)
{ _f = std::forward<F>(f); return *this; }
~basic_dynamic_release() { _f(); }
private:
InterfaceCallable _f;
};
}
using dynamic_release = detail::basic_dynamic_release<std::function<void()>>;
#include <vector>
void do_foo() { std::cout << "Foo\n"; }
void do_bar() { std::cout << "Bar\n"; }
int main(void) {
using InterfaceWrapper = std::shared_ptr<detail::basic_dynamic_release<void(*)(void)>>;
using Thing = InterfaceWrapper::element_type;
{
std::vector<InterfaceWrapper> thing_vector;
auto thing = std::make_shared<Thing>(do_foo);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
} // prints "Foo" once
{
std::vector<InterfaceWrapper> thing_vector;
auto thing = std::make_shared<Thing>(do_foo);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
*thing = do_bar; // Prints nothing
thing_vector.push_back(thing);
} // prints "Bar" once
}
To allow for default constructed instances to have a well defined implementation of the interface, adding a factory (this is to make it very generic):
Live On Coliru
#include <memory>
#include <iostream>
namespace detail {
template <typename T> struct default_construction final {
T operator()() const { return {}; }
};
template <typename InterfaceCallable, typename Factory = default_construction<InterfaceCallable> >
struct basic_dynamic_release {
basic_dynamic_release() = default;
template <typename F> basic_dynamic_release(F&& f) : _f(std::forward<F>(f)) { }
template <typename F> basic_dynamic_release& operator=(F&& f)
{ _f = std::forward<F>(f); return *this; }
~basic_dynamic_release() { _f(); }
private:
InterfaceCallable _f = Factory()();
};
using dynamic_interface = std::function<void()>;
template <typename Factory = default_construction<dynamic_interface> >
using dynamic_release = basic_dynamic_release<dynamic_interface, Factory>;
}
#include <vector>
void do_foo() { std::cout << "Foo\n"; }
void do_bar() { std::cout << "Bar\n"; }
struct foo_default { detail::dynamic_interface operator()() const { return do_foo; } };
int main(void) {
using InterfaceWrapper = std::shared_ptr<detail::dynamic_release<foo_default> >;
using Thing = InterfaceWrapper::element_type;
{
std::vector<InterfaceWrapper> thing_vector;
auto thing = std::make_shared<Thing>();
thing_vector.push_back(thing);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
} // prints "Foo" once
{
std::vector<InterfaceWrapper> thing_vector;
auto thing = std::make_shared<Thing>();
thing_vector.push_back(thing);
thing_vector.push_back(thing);
thing_vector.push_back(thing);
*thing = &do_bar; // Prints nothing
thing_vector.push_back(thing);
} // prints "Bar" once
}
The old answer favoured static polymorphism using boost::variant
, at the cost of being somewhat more complex the manage, but with greater flexibility:
I opted to replace the dynamic polymorphism with static polymorphism, which removes the extra allocation, which also takes with it the lifetime management (what used to be the
unique_ptr
).I think this makes resulting solution a bit simplified, and at the same time more generic (naturally provides some extension points).
Live On Coliru
#include <boost/variant.hpp> #include <memory> #include <iostream> namespace nature { // detail namespace template <typename> struct Nature; template<> struct Nature<struct FooTag> { void do_it() { std::cout << "Foo" << "\n"; } }; template<> struct Nature<struct BarTag> { void do_it() { std::cout << "Bar" << "\n"; } }; using FooNature = Nature<FooTag>; using BarNature = Nature<BarTag>; using AnyNature = boost::variant<FooNature, BarNature>; struct Holder { AnyNature held; ~Holder() { DoIt()(held); } private: struct DoIt : boost::static_visitor<> { void operator()(AnyNature& any) const { return boost::apply_visitor(*this, any); } template <typename N> void operator()(N& nature) const { return nature.do_it(); } }; }; } #include <vector> int main(void) { using InterfaceWrapper = std::shared_ptr<nature::Holder>; using Thing = InterfaceWrapper::element_type; { std::vector<InterfaceWrapper> thing_vector; auto thing = std::make_shared<Thing>(); // FooNature is default thing_vector.push_back(thing); thing_vector.push_back(thing); thing_vector.push_back(thing); thing_vector.push_back(thing); } // prints "Foo" once { std::vector<InterfaceWrapper> thing_vector; auto thing = std::make_shared<Thing>(); thing_vector.push_back(thing); thing_vector.push_back(thing); thing_vector.push_back(thing); thing->held = nature::BarNature {}; // prints nothing thing_vector.push_back(thing); } // prints "Bar" once }
Prints
Foo Bar
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With