The Artima Developer Community
Sponsored Link

The C++ Source
Backyard Hotrodding C++
by Walter Bright
May 23, 2006

<<  Page 2 of 3  >>

Advertisement

Vptr Jamming

Vptrs are one of the typical under-the-hood implementation details of a class. Virtual functions are often implemented by adding a hidden member called a vptr which points to a table of functions called the vtbl[]. A virtual function call is performed by using the vptr to find the vtbl[], and then calling the function at a specific index into that vtbl[]. Therefore, the polymorphic behavior of a class object is controlled by where the hidden vptr member is pointing. The vptr member is set when the object is constructed by some hidden code added by the compiler to every constructor for that object's class.

So, by manipulating the vptr ourselves, we can control the behavior of an object, even change its type, without calling a constructor. This technique is called vptr jamming.

For example, consider a collection class which needs to be very fast. Most of the time, it will be used in a single-threaded manner, but sometimes, in a multithreaded manner. The program can go back and forth between single and multithreaded more than once during execution. It's got to run as fast as possible, so in single-threaded mode the time spent to do locks is unaffordable. So, our class might look like:

struct Collection
{
    ... members of the collection ...

    virtual void foo() = 0;
};

struct SingleThreadedCollection : Collection
{
    void foo()
    {
        ... optimized for single threaded ...
    }
};

struct MultiThreadedCollection : Collection
{
    void foo()
    {
        ... synced for multithreaded ...
    }
};
The desire is to switch back and forth between single and multithreaded operations without having to destruct and reconstruct the object—we want to dynamically change the behavior by switching (i.e. jamming) the vptr. Here's how that would look:
struct Collection
{
    ... members of the collection ...

    virtual void foo() = 0;

    void toSingle();
    void toMulti();
};

struct SingleThreadedCollection : Collection
{
    static SingleThreadedCollection tmp;

    void foo()
    {
        ... optimized for single threaded ...
    }
};

struct MultiThreadedCollection : Collection
{
    static MultiThreadedCollection tmp;

    void foo()
    {
        ... synced for multi threaded ...
    }
};

SingleThreadedCollection SingleThreadedCollection::tmp;
MultiThreadedCollection  MultiThreadedCollection::tmp;

void Collection::toSingle()
{
    *(void **)this = *(void **)&SingleThreadedCollection::tmp;
}

void Collection::toMulti()
{
    *(void **)this = *(void **)&MultiThreadedCollection::tmp;
}
The assignment in the toXxxx() functions gets the value of the right vptr from the static temporary tmp created for just that purpose, and jams it into the vptr location in *this. For most compilers, the vptr is at offset 0 of the struct. For the rest, this code will have to be tweaked to account.

Naturally, there are problems that must be avoided or accounted for here as well:

<<  Page 2 of 3  >>


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us