Is it possible to change a C++ object's class after instantiation?

后端 未结 16 1798
忘掉有多难
忘掉有多难 2021-02-02 06:50

I have a bunch of classes which all inherit the same attributes from a common base class. The base class implements some virtual functions that work in general cases, whilst eac

16条回答
  •  不思量自难忘°
    2021-02-02 07:07

    I have 2 solutions. A simpler one that doesn't preserve the memory address, and one that does preserve the memory address.

    Both require that you provide provide downcasts from Base to Derived which isn't a problem in your case.

    struct Base {
      int a;
      Base(int a) : a{a} {};
      virtual ~Base() = default;
      virtual auto foo() -> void { cout << "Base " << a << endl; }
    };
    struct D1 : Base {
      using Base::Base;
      D1(Base b) : Base{b.a} {};
      auto foo() -> void override { cout << "D1 " << a << endl; }
    };
    struct D2 : Base {
      using Base::Base;
      D2(Base b) : Base{b.a} {};
      auto foo() -> void override { cout << "D2 " << a << endl; }
    };
    

    For the former one you can create a smart pointer that can seemingly change the held data between Derived (and base) classes:

    template  struct Morpher {
      std::unique_ptr obj;
    
      template  auto morph() {
        obj = std::make_unique(*obj);
      }
    
      auto operator->() -> B* { return obj.get(); }
    };
    
    int main() {
      Morpher m{std::make_unique(24)};
      m->foo();        // D1 24
    
      m.morph();
      m->foo();        // D2 24
    }
    

    The magic is in

    m.morph();
    

    which changes the held object preserving the data members (actually uses the cast ctor).


    If you need to preserve the memory location, you can adapt the above to use a buffer and placement new instead of unique_ptr. It is a little more work a whole lot more attention to pay to, but it gives you exactly what you need:

    template  struct Morpher {
      std::aligned_storage_t buffer_;
      B *obj_;
    
      template 
      Morpher(const D &new_obj)
          : obj_{new (&buffer_) D{new_obj}} {
        static_assert(std::is_base_of::value && sizeof(D) == sizeof(B) &&
                      alignof(D) == alignof(B));
      }
      Morpher(const Morpher &) = delete;
      auto operator=(const Morpher &) = delete;
      ~Morpher() { obj_->~B(); }
    
      template  auto morph() {
        static_assert(std::is_base_of::value && sizeof(D) == sizeof(B) &&
                      alignof(D) == alignof(B));
    
        obj_->~B();
        obj_ = new (&buffer_) D{*obj_};
      }
    
      auto operator-> () -> B * { return obj_; }
    };
    
    int main() {
      Morpher m{D1{24}};
      m->foo(); // D1 24
    
      m.morph();
      m->foo(); // D2 24
    
      m.morph();
      m->foo(); // Base 24
    }
    

    This is of course the absolute bare bone. You can add move ctor, dereference operator etc.

提交回复
热议问题