Search code examples
c++c++11g++undefined-behaviorvptr

Obtain the vtable of a class without an object


I am attempting to implement a system similar to the first described here. That is, the (ab)use of vtable modification to change object behavior at runtime. This is part of my attempts to create an efficient type-generic wrapper in a C++ project I am working on.

The example, should you be unable to access it, copies the vtable using memcpy() and the this pointer as such:

void setType( const DataType& newType )
{
    memcpy( this, &newType, sizeof(DataType) );
}

However, I have an issue with this method: I do not have an object of the target class to copy the vtable from, and do not want to have to create one, as some types are costly to construct.

Is there a way to access the vtable which would be emplaced into an object of a given class without an object of that class?

It would be preferable if it were somewhat portable, but I have largely resigned to this being compiler-specific; as such, a GCC/G++ only method would be acceptable if there is no other option. Let us also assume I am only concerned with building this on fairly standard OSes and architectures.

I am using C++11, should that aid in this somehow.

Edit: I want to be completely clear, I know how dangerous this sort of behavior is. I'm more interested in the idea and perhaps its narrow application in very controlled circumstances than I am in it being a good idea for production software, despite what my intro might suggest.


Solution

  • struct many_vtable {
      void(*dtor)(void*);
      void(*print)(void const*,std::ostream&);
    };
    template<class T>struct tag_t{};
    template<class T>constexpr tag_t<T> tag = {};
    
    template<class T>
    many_vtable const* make_many_vtable(tag_t<T>){
      static const many_vtable retval = {
        // dtor
        [](void* p){
          reinterpret_cast<T*>(p)->~T();
        },
        // print
        [](void const*p, std::ostream& os){
          os<<*reinterpret_cast<T const*>(p);
        }
      };
      return &retval;
    }
    struct many {
      many_vtable const* vtable=nullptr;
      std::aligned_storage_t<100, alignof(double)> buff;
      void clear(){if(vtable) vtable->dtor(&buff);vtable=nullptr;}
      ~many(){ clear(); }
      many()=default;
      many(many const&)=delete; // not yet supported
      many& operator=(many const&)=delete; // not yet supported
    
      explicit operator bool()const{return vtable!=nullptr;}
    
      template<class T,class...Args>
      void emplace(Args&&...args){
        static_assert(alignof(T) <= alignof(double), "not enough alignment");
        static_assert(sizeof(T) <= 100, "not enough size");
        clear();
        ::new((void*)&buff) T(std::forward<Args>(args)...);
        vtable=make_many_vtable(tag<T>);
      }
      friend std::ostream& operator<<(std::ostream& os, many const&m){
        if(!m.vtable) return os;
        m.vtable->print(&m.buff, os);
        return os;
      }
    };
    

    This is a manual vtable design. It can store anything up to 100 bytes whose alignment is less than a double that can be printed to a stream.

    'Erasing' down to more (or different) operations is easy. For example, being able to copy/move to another many.

    It does not violate the standard, and it has similar overhead to the linked example.

    many m;
    m.emplace<int>(3);
    std::cout << m << '\n';
    m.emplace<double>(3.14);
    std::cout << m << '\n';
    

    live example.

    This is manual vtables, because we are basically reimplementing the vtable concept manually.