Friday, 27 February 2015

Snippet: Class Properties in C++11

Update: I discovered several bugs with the initial implementation, so I've updated this post with a more complete solution.

Properties as seen in Python and C# are a pretty nice language feature, they allow you to have the syntax of accessing a public member variable, yet have that access still go through a getter or setter function.

I have to say I'm not totally sold on RW properties (I feel that if you are intending to change the state of a class, you should be forced to make it clear by calling a setter function) but I like RO properties, they remove the need to call a function to just access a value, and still allow you to check pre/post conditions or log access.

C++ doesn't have properties, if you Google around there's a general consensus that they don't fit the language well. However, I got a bit tired of my game engine's API having an excessive number of function calls. Here's an example:

TextureID rtt = window().new_texture(false);
window().mesh(rect_mesh)->set_texture_on_material(0, rtt);

It would be far nicer to be able to write:

TextureID rtt = window->new_texture(false);
window->mesh(rect_mesh)->set_texture_on_material(0, rtt);

... and still have the control that a getter function provides. So I wrote a Property template class, it looks like this..

template<typename Container, typename T>
class Property {
public:
    Property(Container* _this, T Container::* member):
        this_(_this),
        getter_([member](Container* self) -> T& {
            return self->*member;
        }) {
    }

    Property(Container* _this, T* Container::* member):
        this_(_this),
        getter_([member](Container* self) -> T& {
            return *(self->*member);
        }) {
    }

    Property(Container* _this, std::shared_ptr<T> Container::* member):
        this_(_this),
        getter_([member](Container* self) -> T& {
            return *(self->*member);
        }) {
    }

    Property(Container* _this, std::function<T& (Container*)> getter):
        this_(_this),
        getter_(getter) {

    }

    /*
     *  We can't allow copy construction, because 'this_' will never be initialized
     */
    Property(const Property& rhs) = delete;

    Property operator=(const Property& rhs) {
        assert(this_); //Make sure that this_ was initialized

        getter_ = rhs.getter_;
        // Intentionally don't transfer 'this_'
    }

    inline operator T&() const { return getter_(this_); }
    inline T* operator->() const { return &getter_(this_); }
private:
    Container* this_ = nullptr;
    std::function<T& (Container*)> getter_;
};



Container is the class you are adding the property to, T is the type of the property.

Thanks to C++11 allowing initialization of members in the body of the class, and the awesome syntax of lambdas, we can use the Property template like so:

Property<WindowBase, Watcher> watcher = {
    this, [](const WindowBase* self) -> Watcher& {
        if(!self->watcher_) {
            throw LogicError("Watcher has not been initialized");
        } else {
            return *self->watcher_.get();
        }
    }
};

Or, if you just want to wrap a member variable:

Property<WindowBase, Console> console = { this, &WindowBase::console_ };

Unfortunately we can't override the '.' operator in C++ so we can't get the exact same syntax as C# or Python, but the -> operator is good enough (and as a lot of my engine's accessors return pointers, it actually made things more consistent).

No comments:

Post a Comment