Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

On what platforms will this crash, and how can I improve it?

I've written the rudiments of a class for creating dynamic structures in C++. Dynamic structure members are stored contiguously with (as far as my tests indicate) the same padding that the compiler would insert in the equivalent static structure. Dynamic structures can thus be implicitly converted to static structures for interoperability with existing APIs.

Foremost, I don't trust myself to be able to write Boost-quality code that can compile and work on more or less any platform. What parts of this code are dangerously in need of modification?

I have one other design-related question: Is a templated get accessor the only way of providing the compiler with the requisite static type information for type-safe code? As it is, the user of dynamic_struct must specify the type of the member they are accessing, whenever they access it. If that type should change, all of the accesses become invalid, and will either cause spectacular crashes—or worse, fail silently. And it can't be caught at compile time. That's a huge risk, and one I'd like to remedy.

Example of usage:

struct Test {

    char a, b, c;
    int i;
    Foo object;

};

void bar(const Test&);

int main(int argc, char** argv) {

    dynamic_struct<std::string> ds(sizeof(Test));

    ds.append<char>("a") = 'A';
    ds.append<char>("b") = '2';
    ds.append<char>("c") = 'D';
    ds.append<int>("i") = 123;
    ds.append<Foo>("object");
    bar(ds);

}

And the code follows:

//
// dynamic_struct.h
//
// Much omitted for brevity.
//


/**
 * For any type, determines the alignment imposed by the compiler.
 */
template<class T>
class alignment_of {
private:

    struct alignment {

        char a;
        T b;

    }; // struct alignment

public:

    enum { value = sizeof(alignment) - sizeof(T) };

}; // class alignment_of


/**
 * A dynamically-created structure, whose fields are indexed by keys of
 * some type K, which can be substituted at runtime for any structure
 * with identical members and packing.
 */
template<class K>
class dynamic_struct {
public:


    // Default maximum structure size.
    static const int DEFAULT_SIZE = 32;


    /**
     * Create a structure with normal inter-element padding.
     */
    dynamic_struct(int size = DEFAULT_SIZE) : max(size) {

        data.reserve(max);

    } // dynamic_struct()


    /**
     * Copy structure from another structure with the same key type.
     */
    dynamic_struct(const dynamic_struct& structure) :
        members(structure.members), max(structure.max) {

        data.reserve(max);

        for (iterator i = members.begin(); i != members.end(); ++i)
            i->second.copy(&data[0] + i->second.offset,
                &structure.data[0] + i->second.offset);

    } // dynamic_struct()


    /**
     * Destroy all members of the structure.
     */
    ~dynamic_struct() {

        for (iterator i = members.begin(); i != members.end(); ++i)
            i->second.destroy(&data[0] + i->second.offset);

    } // ~dynamic_struct()


    /**
     * Get a value from the structure by its key.
     */
    template<class T>
    T& get(const K& key) {

        iterator i = members.find(key);

        if (i == members.end()) {

            std::ostringstream message;
            message << "Read of nonexistent member \"" << key << "\".";
            throw dynamic_struct_access_error(message.str());

        } // if

        return *reinterpret_cast<T*>(&data[0] + i->second.offset.offset);

    } // get()


    /**
     * Append a member to the structure.
     */
    template<class T>
    T& append(const K& key, int alignment = alignment_of<T>::value) {

        iterator i = members.find(key);

        if (i != members.end()) {

            std::ostringstream message;
            message << "Add of already existing member \"" << key << "\".";
            throw dynamic_struct_access_error(message.str());

        } // if

        const int modulus = data.size() % alignment;
        const int delta = modulus == 0 ? 0 : sizeof(T) - modulus;

        if (data.size() + delta + sizeof(T) > max) {

            std::ostringstream message;

            message << "Attempt to add " << delta + sizeof(T)
                << " bytes to struct, exceeding maximum size of "
                << max << ".";

            throw dynamic_struct_size_error(message.str());

        } // if

        data.resize(data.size() + delta + sizeof(T));

        new (static_cast<void*>(&data[0] + data.size() - sizeof(T))) T;

        std::pair<iterator, bool> j = members.insert
            ({key, member(data.size() - sizeof(T), destroy<T>, copy<T>)});

        if (j.second) {

            return *reinterpret_cast<T*>(&data[0] + j.first->second.offset);

        } else {

            std::ostringstream message;
            message << "Unable to add member \"" << key << "\".";
            throw dynamic_struct_access_error(message.str());

        } // if

    } // append()


    /**
     * Implicit checked conversion operator.
     */
    template<class T>
    operator T&() { return as<T>(); }


    /**
     * Convert from structure to real structure.
     */
    template<class T>
    T& as() {

        // This naturally fails more frequently if changed to "!=".
        if (sizeof(T) < data.size()) {

            std::ostringstream message;

            message << "Attempt to cast dynamic struct of size "
                << data.size() << " to type of size " << sizeof(T) << ".";

            throw dynamic_struct_size_error(message.str());

        } // if

        return *reinterpret_cast<T*>(&data[0]);

    } // as()


private:


    // Map from keys to member offsets.
    map_type members;

    // Data buffer.
    std::vector<unsigned char> data;

    // Maximum allowed size.
    const unsigned int max;


}; // class dynamic_struct
like image 983
Jon Purdy Avatar asked Nov 05 '22 08:11

Jon Purdy


2 Answers

There's nothing inherently wrong with this kind of code. Delaying type-checking until runtime is perfectly valid, although you will have to work hard to defeat the compile-time type system. I wrote a homogenous stack class, where you could insert any type, which functioned in a similar fashion.

However, you have to ask yourself- what are you actually going to be using this for? I wrote a homogenous stack to replace the C++ stack for an interpreted language, which is a pretty tall order for any particular class. If you're not doing something drastic, this probably isn't the right thing to do.

In short, you can do it, and it's not illegal or bad or undefined and you can make it work - but you only should if you have a very desperate need to do things outside the normal language scope. Also, your code will die horrendously when C++0x becomes Standard and now you need to move and all the rest of it.

The easiest way to think of your code is actually a managed heap of a miniature size. You place on various types of object.. they're stored contiguously, etc.

Edit: Wait, you didn't manage to enforce type safety at runtime either? You just blew compile-time type safety but didn't replace it? Let me post some far superior code (that is somewhat slower, probably).

Edit: Oh wait. You want to convert your dynamic_struct, as the whole thing, to arbitrary unknown other structs, at runtime? Oh. Oh, man. Oh, seriously. What. Just no. Just don't. Really, really, don't. That's so wrong, it's unbelievable. If you had reflection, you could make this work, but C++ doesn't offer that. You can enforce type safety at runtime per each individual member using dynamic_cast and type erasure with inheritance. Not for the whole struct, because given a type T you can't tell what the types or binary layout is.

like image 156
Puppy Avatar answered Nov 09 '22 14:11

Puppy


I think the type-checking could be improved. Right now it will reinterpret_cast itself to any type with the same size.

Maybe create an interface to register client structures at program startup, so they may be verified member-by-member — or even rearranged on the fly, or constructed more intelligently in the first place.

#define REGISTER_DYNAMIC_STRUCT_CLIENT( STRUCT, MEMBER ) \
    do dynamic_struct::registry< STRUCT >() // one registry obj per client type \
        .add( # MEMBER, &STRUCT::MEMBER, offsetof( STRUCT, MEMBER ) ) while(0)
        //    ^ name as str ^ ptr to memb ^ check against dynamic offset
like image 43
Potatoswatter Avatar answered Nov 09 '22 13:11

Potatoswatter