CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 12 of 12

Thread: Semantics

  1. #1
    Join Date
    Aug 2008
    Posts
    902

    Semantics

    I think that an important thing to accomplish in any API is to separate the interface from the implementation. One way of doing this is through the pimpl idiom. Another way is through the use of Interfaces, which are essentially Pure Abstract Classes. I like this approach a lot, because it allows for late binding and completely hides implementation (keep those public header files light and devoid of implementation details).

    Example:

    Code:
    //Window.h - part of the public API
    
    class IWindow
    {
    public:
        static IWindow* New(int width, int height);
        virtual ~IWindow() {}
        virtual void setSize(int width, int height) = 0;
        virtual void setTitle(const std::string& title) = 0;
    }; //notice no implementation details here
    
    //MyWindowImpl.h - private header
    
    #include "Window.h"
    class MyWindowImpl : public IWindow
    {
    public:
        MyWindowImpl(int width, int height);
        void setSize(int width, int height);
        void setTitle(const std::string& title);
    };
    
    //Window.cpp - code file, not a part of the public API
    
    #include "Window.h"
    #include "MyWindowImpl.h"
    
    IWindow* IWindow::New(int width, int height)
    {
        //we could dynamically choose which type of window to return
        return new MyWindowImpl(int width, int height);
    }
    
    //example usage
    
    #include "Widnow.h"
    
    int main()
    {
        IWindow* wnd = IWindow::New(640, 480);
        wnd->setTitle("Hello, World!");
        delete wnd;
    }
    That's great, because the only thing the user need be exposed to is the interface. However, the one disadvantage is that you are stuck with pointers. I think most modern C++ programmers try to avoid pointers as much as possible. The STL for example, is an API mostly devoid of pointers. We don't create pointers to string or stream objects after all. I think most programmers expect a good modern API to have value semantics similar to:

    Code:
    int main()
    {
        Window wnd(640, 480);
        wnd.setTitle("Hello, World!");
    }
    They want to be able to create their objects on the stack and benefit from RAII. Wouldn't it be nice if we could have our cake and eat it too? One solution would be to use smart pointers. We could do this instead:

    Code:
    //Window.h
    class IWindow
    {
    public:
        typedef std::shared_ptr<IWindow> Ptr;
        ...
    }
    
    //example
    
    int main()
    {
        IWindow::Ptr wnd(IWindow::New(640, 480));
        wnd->setTitle("Hello, World!");
    }
    You still have pointer semantics, but at least you don't have to worry about managing it's lifetime. However, I was working on a slightly different approach which may bring the it a bit closer to value semantics.

    Code:
    //Window.h
    
    #include "SmartRef.h"
    
    class IWindow
    {
    public:
    	virtual ~IWindow() {}
    	virtual void setSize(int width, int height) = 0;
    	virtual void setTitle(const std::string& title) = 0;
    }; //very clean interface definition
    
    typedef SmartRef<IWindow, int, int> Window;
    
    
    //SmartRef.h
    
    template <class T, class... InitArgs>
    class SmartRef
    {
    public:
    	typedef SmartRef<T, InitArgs...> ref_type;
    	static ref_type New(InitArgs...) {}
    	SmartRef(InitArgs... args)
    	{
    		*this = ref_type::New(args...);
    	}
    	SmartRef(T* ptr)
    		:	_ptr(ptr)
    	{}
    	T* operator->() { return _ptr.get(); }
    private:
    	std::shared_ptr<T> _ptr;
    };
    
    
    //Window.cpp
    
    #include "MyWindowImpl.h"
    
    template<>
    Window Window::New(int width, int height)
    {
    	return new MyWindowImpl(width, height);
    }
    
    
    //example usage
    
    #include "Window.h"
    
    int main()
    {
        Window wnd(640, 480); //alternatively Window wnd = Window::New(640, 480);
        wnd->setTitle("Hello, World!");
    }
    You are still stuck using the pointer operator, but everything else about it is like value semantics. These semantics look similar to Google's V8 API.

    What exactly are your thoughts?
    Last edited by Chris_F; October 30th, 2011 at 08:22 PM.

  2. #2
    Join Date
    Oct 2008
    Posts
    1,456

    Re: Semantics

    Quote Originally Posted by Chris_F View Post
    You are still stuck using the pointer operator, but everything else about it is like value semantics.
    the point of value semantics is of having types resembling scalar primitives in the way they are used; copyability/movability is a necessary but not a sufficient condition for it. So, I wouldn't say that your Window type has a value semantics.

    That said, I feel smart pointers alone ( eventually, with a good factory design ) more than adequate for the purpose of manipulating OO interfaces ...

  3. #3
    Join Date
    Aug 2008
    Posts
    902

    Re: Semantics

    Quote Originally Posted by superbonzo View Post
    That said, I feel smart pointers alone ( eventually, with a good factory design ) more than adequate for the purpose of manipulating OO interfaces ...
    Would you care to provide an example?

  4. #4
    Join Date
    Jan 2006
    Location
    Singapore
    Posts
    6,765

    Re: Semantics

    Would you care to provide an example in which they are inadequate for that purpose?
    C + C++ Compiler: MinGW port of GCC
    Build + Version Control System: SCons + Bazaar

    Look up a C/C++ Reference and learn How To Ask Questions The Smart Way
    Kindly rate my posts if you found them useful

  5. #5
    Join Date
    Aug 2008
    Posts
    902

    Re: Semantics

    Quote Originally Posted by laserlight View Post
    Would you care to provide an example in which they are inadequate for that purpose?
    I was asking for an example of what he would consider good factory design with regards to smart pointers.

  6. #6
    Join Date
    Aug 2008
    Posts
    902

    Re: Semantics

    How about this alternative approach?

    Code:
    class Window
    {
    public:
        static Window* New(int width, int height);
        virtual ~Window() {}
        virtual void setSize(int width, int height) = 0;
        virtual void setTitle(const std::string& title) = 0;
    };
    Code:
    template <class T>
    class Handle : public std::shared_ptr<T>
    {
    public:
        template <class... Args>
        Handle(Args... args)
            : std::shared_ptr<T>(T::New(args...))
        {}
    };
    Code:
    int main()
    {
        Handle<Window> wnd(640, 480);
        wnd->setTitle("Hello, World");
    }
    I guess the intent here is not so much to have value semantics per se, but rather to simply have a little more sugar than simply using shared_ptr by itself.

  7. #7
    Join Date
    Jun 2008
    Posts
    592

    Re: Semantics

    I wouldn't inherit a shared_ptr since the destructor isn't virtual. Also shared_ptr only destroys the object it contains only after all shared_ptrs destruct, so if a user clicks the exit button, you will still have to release the object and shared_ptr doesn't have a direct way.

    Also const std::string& title is not unicode compatible. I would make a new typedef called tstring that will support unicode or non-unicode depending on compiler settings.
    0100 0111 0110 1111 0110 0100 0010 0000 0110 1001 0111 0011 0010 0000 0110 0110 0110 1111 0111 0010
    0110 0101 0111 0110 0110 0101 0111 0010 0010 0001 0010 0001 0000 0000 0000 0000
    0000 0000 0000 0000

  8. #8
    Join Date
    Aug 2008
    Posts
    902

    Re: Semantics

    Quote Originally Posted by Joeman View Post
    I wouldn't inherit a shared_ptr since the destructor isn't virtual.
    I thought that was only an issue if you were using it polymorphically. I didn't intend on somebody using something like std::shared_ptr<Window>* p = new Handle<Window>.

    Quote Originally Posted by Joeman View Post
    Also const std::string& title is not unicode compatible. I would make a new typedef called tstring that will support unicode or non-unicode depending on compiler settings.
    I mostly work on Unix systems, and I design all my APIs to be UTF-8 aware. I don't use that Microsoft tstring construct.

  9. #9
    Join Date
    Jan 2006
    Location
    Singapore
    Posts
    6,765

    Re: Semantics

    Quote Originally Posted by Chris_F
    I guess the intent here is not so much to have value semantics per se, but rather to simply have a little more sugar than simply using shared_ptr by itself.
    So your primary motivation is to replace this:
    Code:
    std::shared_ptr<Window> wnd(Window::New(640, 480));
    with:
    Code:
    Handle<Window> wnd(640, 480);
    I don't think there is much benefit, especially since a maintainer would need to lookup just what is a Handle class template and what operations it offers, whereas he/she should be familiar with std::shared_ptr and hence can get on with the meat of the code. If you really wanted another name, I guess you could use a typedef and then write:
    Code:
    WindowHandle wnd(Window::New(640, 480));
    Quote Originally Posted by Chris_F
    I thought that was only an issue if you were using it polymorphically. I didn't intend on somebody using something like std::shared_ptr<Window>* p = new Handle<Window>.
    Yeah, but it is precisely because you don't intend that that you should not inherit from std::shared_ptr, unless you are inheriting privately.
    C + C++ Compiler: MinGW port of GCC
    Build + Version Control System: SCons + Bazaar

    Look up a C/C++ Reference and learn How To Ask Questions The Smart Way
    Kindly rate my posts if you found them useful

  10. #10
    Lindley is offline Elite Member Power Poster
    Join Date
    Oct 2007
    Location
    Seattle, WA
    Posts
    10,895

    Re: Semantics

    Quote Originally Posted by Chris_F View Post
    I thought that was only an issue if you were using it polymorphically. I didn't intend on somebody using something like std::shared_ptr<Window>* p = new Handle<Window>.
    In that case, private inheritance may be more appropriate.

  11. #11
    Join Date
    Aug 2008
    Posts
    902

    Re: Semantics

    Ah, I see. Private inheritance is not something I've had much experience with. So I could do this instead?

    Code:
    template <class T>
    class Handle : private std::shared_ptr<T>
    {
    public:
    	explicit Handle(T* ptr) 
    		: std::shared_ptr<T>(ptr)
    	{}
    	template <class... Args>
    	Handle(Args... args)
    		: std::shared_ptr<T>(T::New(args...))
    	{}
    	using std::shared_ptr<T>::operator->;
    	using std::shared_ptr<T>::get;
    };

  12. #12
    Lindley is offline Elite Member Power Poster
    Join Date
    Oct 2007
    Location
    Seattle, WA
    Posts
    10,895

    Re: Semantics

    When it comes right down to it, private inheritance isn't all that different from composition. In many cases, composition is a better option. In any event, nothing will break if you use public inheritance; it's just a matter of preventing the compiler from allowing operations that wouldn't work.

    http://www.parashift.com/c++-faq-lit....html#faq-24.2

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured