最近一直在thinking inmodern C++,大部分都是使用基于对象(object based)的技术。使得对象相较于C++的面向对象风格的程序而言,对象要散列一些,结构更清晰。对象之间适配使用了大量的泛型编程(Generic programming)技术。Boost C++库的发展和C++11语言新特性对模板的支持,优秀的泛型库大量涌现。它们都继承了STL极高的可复用性与很低的学习曲线。Jeremy Ong写的Selene库是笔者目前阅读过的非常精彩的modern C++风格的程序库,但也有一些show off的感觉。还有两个数学库glm与mtl。
对于图形API的C++ wapper库确实不多,OGLplus算是一个。代码并算不上是非常精彩,诸如没有使用traits/policy编程技法来降低复杂性,使用了decltype关键字这种较为初学的泛型技术,没有考虑空基类优化等细节问题。但也有很多出彩的地方,比如对OpenGL具名对象的封装(参见模板类Named)以及对OpenGL的Operation的封装达到完全消除对OpenGL API函数的裸调用(Raw Call)等。
OGLplus是一个仅包含头文件(header only)的modern C++风格的库。它主要是对OpenGL的C++包装(Wrapper)。官网上面,它自称是面向对象(Object oriented)的OpenGL外观,但是笔者认为它是基于对象的(Object based)。下面用一段代码来展示OGLplus的Object based风格的使用。
#include <cassert>
#include <iostream>
#include <GL/glew.h>
#include <GL/glut.h>
#include <oglplus/all.hpp>
class Example
{
private:
oglplus::Context gl;
oglplus::VertexShader vs;
oglplus::FragmentShader fs;
oglplus::Program prog;
oglplus::Buffer verts;
public:
Example(void)
{
using namespace oglplus;
vs.Source(" \
#version 330\n \
in vec3 Position; \
void main(void) \
{ \
gl_Position = vec4(Position, 1.0); \
} \
");
vs.Compile();
fs.Source(" \
#version 330\n \
out vec4 fragcolor; \
void main(void) \
{ \
fragcolor = vec4(1.0, 0.0, 0.0, 1.0); \
} \
");
fs.Compile();
prog.AttachShader(vs);
prog.AttachShaders(fs);
prog.Link();
prog.Use();
GLfloat triangle_verts[9] = {
0.0f, 0.0f, 0.0f,
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f
};
verts.Bind(Buffer::Target::Array);
verts.Data(Buffer::Target::Array, 9, triangle_verts);
VertexAttribArray vert_attr(prog, "Position");
vert_attr.Setup<GLfloat>(3);
vert_attr.Enable();
gl.ClearColor(0.0f, 0.0f, 0.0f, 0.0f);
gl.ClearDepth(1.0f);
}
void Display(void)
{
using namespace oglplus;
gl.Clear().ColorBuffer().DepthBuffer();
gl.Viewport( 0,0, 800, 600 );
gl.DrawArrays(PrimitiveType::TriangleStrip, 0, 3);
}
static Example& GetInstance()
{
static Example example;
return example;
}
};
static void Display(void)
{
Example::GetInstance().Display();
glutSwapBuffers();
}
int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);
glutInitWindowSize(800, 600);
glutInitWindowPosition(100,100);
glutCreateWindow("OGLplus+GLUT+GLEW");
if(glewInit() == GLEW_OK) try
{
glGetError();
glutDisplayFunc(Display);
glutMainLoop();
return 0;
}
catch(oglplus::Error& err)
{
std::cerr <<
"Error (in " << err.GLSymbol() << ", " <<
err.ClassName() << ": '" <<
err.ObjectDescription() << "'): " <<
err.what() <<
" [" << err.File() << ":" << err.Line() << "] ";
std::cerr << std::endl;
err.Cleanup();
}
return 1;
}
Example类包裹的是OpenGL标准,这也是OGLplus所做的所有关于OpenGL的事情。main函数与一个glut的渲染回调::Display()都是与平台gl实现相关的代码,这些OGLplus并没有考虑,也算是合理的。虽然OGLplus在OpenGL API外层封装了C++风格的Wrapper,但OGLplus保留了OpenGL的状态机编程风格,使得拥有一定OpenGL编程经验的程序员能够非常快的上手。OGLplus的封装到底做了哪些事情呢?
OGLplus是类型安全的OpenGL包装。OpenGL管理创建在OpenGL环境(context, 在不同实现中由不同的系统内核对象管理)下的名字对象(如Buffer,Texture,Shader,FrameBuffer等),通常用一个GLuint类型的标识。这种无型别的标识使得程序在运行期出错的几率大大增加。资源在创建时便已知其型别,使用C++型别来管理这些OpenGL资源,应属上策。OpenGL的名字对象由oglplus::Named类统一管理,这也是官网提及的资源自动管理的特性支持的基础组件。OGLplus为我们封装了所有的OpenGL函数调用,并同时封装了调用这些函数的异常处理,并抛出C++异常。OGLplus的封装,是不是让代码变得clean了许多呢?
最后再来谈谈我觉得这段代码一个非常出彩的地方。就是Example::Display方法中的gl.Clear().ColorBuffer().DepthBuffer()一句。这一句fluent interface风格的代码中的三次函数调用创建了三个oglplus::context::ClrBits的对象,Clear()创建一个空对象为了接下来的调用提供interface,后面依然依次创建ClrBIts型别的对象,并为后面的调用提供接口。创建的对象都是临时对象,离开代码段即进行对象的析构,而在ClrBits型别的析构函数中调用的则是glClear方法。这是使用RAII对象的良好实践。
好了,Outline先写到这里,modern C++设计的旅程刚刚开始。