C ++ General pointers. How can I change the pointer to the base object for all copies?

I have a weird situation with virtual classes, and I need help with the design.

  • Objects work when destroyed,
  • The objects are stored in the vector, but I need to get the object or a link to it from the vector, change it and change this reflection in ALL "copies" of the object,
  • I would like the objects to be copied.

I have a solution, but I am looking for the best. This is the code I wrote that does what I'm looking for, but it depends on pointers to pointers to pointers.

It seems to me that if I could directly manipulate the main data pointer of a common pointer, I could get away with one lower level of abstraction.

I would like to do this using 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, but I'm especially interested in solutions that work in C ++ 03 using boost (my example is C ++ 11, but my "real" code is C ++ 03 using boost :: shared_ptr).

EXPLANATION

I'm basically looking for a better way to implement InterfaceWrapper2 in my code sample. main() is the best explanation of what I'm trying to accomplish. Just remember that the behavior is stuck in these virtual classes.

+5
source share
2 answers

Borrowing from sehe a brilliant understanding , this version gives you something that is closer to what you can do in C ++. 2003:

 class InterfaceWrapper { typedef std::unique_ptr<Interface> UI; std::shared_ptr<UI> p_; public: InterfaceWrapper () : p_(std::make_shared<UI>(new Foo)) {} ~InterfaceWrapper () { if (p_.unique()) (*p_)->WriteIt(); } void MakeBar() { p_->reset(new Bar); } const UI & operator -> () { return *p_.get(); } }; 

Instead of user deletion, I decided to add a condition to the InterfaceWrapper destructor. This allowed the constructor to use std::make_shared .

0
source

This is what you need?

Update 3

If you want to preserve the interface hardware, then a rather short way to write a shell is by simply creating standard library functions:

 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); } }; 

Watch Live On Coliru

Update 2

  • Realizing that std::function<> 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 optimization, if you want to achieve greater efficiency using stateless functions, add the basic_dynamic_release template, which allows you to use different types of functors (for example, 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 } 
  • So that the default allowed instances have a clearly defined interface implementation by adding a factory (this makes it very general):

    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 } 

Old answer

The old answer favors static polymorphism using boost::variant , at the cost of more complex controls, but with more flexibility:

I decided to replace dynamic polymorphism with static polymorphism, which removes the extra selection, which also takes with it the life cycle management (which used to be unique_ptr ).

I think this simplifies the simplified solution and at the same time more general (of course, gives 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 } 

Print

 Foo Bar 
+3
source

Source: https://habr.com/ru/post/1208192/


All Articles