|
-
October 30th, 2011, 08:16 PM
#1
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.
-
October 31st, 2011, 03:57 AM
#2
Re: Semantics
 Originally Posted by Chris_F
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 ...
-
October 31st, 2011, 09:22 AM
#3
Re: Semantics
 Originally Posted by superbonzo
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?
-
October 31st, 2011, 09:27 AM
#4
Re: Semantics
Would you care to provide an example in which they are inadequate for that purpose?
-
October 31st, 2011, 10:01 AM
#5
Re: Semantics
 Originally Posted by laserlight
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.
-
October 31st, 2011, 04:18 PM
#6
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.
-
October 31st, 2011, 05:30 PM
#7
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
-
October 31st, 2011, 06:12 PM
#8
Re: Semantics
 Originally Posted by Joeman
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>.
 Originally Posted by Joeman
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.
-
October 31st, 2011, 09:40 PM
#9
Re: Semantics
 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));
 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.
-
November 1st, 2011, 10:39 AM
#10
Re: Semantics
 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>.
In that case, private inheritance may be more appropriate.
-
November 1st, 2011, 12:45 PM
#11
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;
};
-
November 1st, 2011, 04:33 PM
#12
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
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|