Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

User-friendly API with unique_ptr

I am implementing a simple GUI for OpenGL, mostly as an exercise for myself. The idea is to have a Gui class, where each instance can be assigned to a different render target (e.g. back buffer or texture). GUI elements (widgets) are assigned to exactly one instance of the Gui class. I suppose storing elements inside a GUI is a typical use case for unique_ptr. Here is what I came up with:

class Element {
public:
    Element();
    virtual ~Element();

    static std::unique_ptr<Element> create_unique();
};

class Gui {
public:    
    typedef std::unique_ptr<Element> element_ptr;    
    Gui();
    void addElement( element_ptr element );
    void addElementRaw( Element* element );

private:
    std::list<element_ptr> elements;
};

int main(void) {
    Gui gui;

    gui.addElementRaw( new Element() ); // 1

    gui.addElement( Element::create_unique() );  // 2

    gui.addElement( std::unique_ptr<Element>(new Element()) ); // 3

    auto el = Element::create_unique();
    gui.addElement( std::move(el) ); // 4
}

I don't want a potential user of the GUI have worrying about moving the pointer around. However, I want to make it clear from the API that the GUI class takes ownership of the Element.

  1. Passing a raw pointer: Simple usage, but the API does not make it clear that ownership was passed.
  2. factory function: Simple usage, but the function needs to be re-implemented for each class derived from Element.
  3. Manually creating a unique_ptr: cumbersome for the user
  4. Move semantic: seems cumbersome too.

I am not happy with my solution(s). What I want is the simplicity of (1) while making it clear from the API that the gui now owns the element.

like image 258
MB-F Avatar asked Jan 04 '14 17:01

MB-F


1 Answers

How about just providing the arguments for the new element?

template< typename... T >
void addElement( T&&... t )
{
    elements.emplace_back( std::unique_ptr< Element >( new Element( std::forward< T >( t )... ) ) );
}

With C++14, you could also use std::make_unique:

template< typename... T >
void addElement( T&&... t )
{
    elements.emplace_back( std::make_unique< Element >( std::forward< T >( t )... ) );
}

In case you want to create elements derived from Element, you could also do this (C++14 version):

template< typename C, typename... T >
void emplace_back( T&&... t )
{
    elements.emplace_back( std::make_unique< C >( std::forward< T >( t )... ) );
}

and it can be used like this:

gui.emplace_back< Element >();

// insert a class derived from Element (hope you have a virtual dtor!)
gui.emplace_back< DerivedFromElement >();

// calls Element::Element( int, const char* ) or similar...
gui.emplace_back< Element >( 42, "Hallo" );
like image 99
Daniel Frey Avatar answered Oct 23 '22 07:10

Daniel Frey