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:
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://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; }
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:int main() { Window wnd(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 class IWindow { public: typedef std::shared_ptr<IWindow> Ptr; ... } //example int main() { IWindow::Ptr wnd(IWindow::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.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!"); }
What exactly are your thoughts?




Reply With Quote