Tho*_*mas 21 c++ constructor destructor design-patterns
在OpenGL中,人们经常编写如下代码:
glPushMatrix();
// modify the current matrix and use it
glPopMatrix();
Run Code Online (Sandbox Code Playgroud)
本质上,状态已更改,然后执行一些使用新状态的操作,最后恢复状态.
现在这里有两个问题:
在真正的基于对象的编程风格中,我编写了一些实用程序类来克服这些问题,如下所示:
struct WithPushedMatrix {
WithPushedMatrix() { glPushMatrix(); }
~WithPushedMatrix() { glPopMatrix(); }
};
Run Code Online (Sandbox Code Playgroud)
现在我可以简单地写下我之前的例子:
WithPushedMatrix p;
// modify the current matrix and use it
Run Code Online (Sandbox Code Playgroud)
恢复的确切时刻取决于生命周期p.如果抛出异常,则会p调用析构函数,恢复状态,并且生命是好的.
不过,我并不完全开心.特别是如果构造函数接受一些参数(例如flags glEnable),很容易忘记将对象赋值给变量:
WithEnabledFlags(GL_BLEND); // whoops!
Run Code Online (Sandbox Code Playgroud)
临时性立即被破坏,状态变化过早地被逆转.
另一个问题是,阅读我的代码的其他人可能会感到困惑:"为什么这里声明的变量从未被使用?让我们摆脱它!"
所以,我的问题:这是一个好模式吗?它甚至可能有名字吗?我忽略了这种方法有什么问题吗?最后但并非最不重要的:有什么好的选择吗?
更新:是的,我想这是RAII的一种形式.但不是通常使用RAII的方式,因为它涉及一个看似无用的变量; 永远不会明确访问有问题的"资源".我只是没有意识到这种特殊用法是如此常见.
tza*_*man 24
我喜欢使用RAII控制OpenGL状态的想法,但我实际上更进一步:让你的WithFoo类构造函数将一个函数指针作为参数,它包含你想在该上下文中执行的代码.然后不要创建命名变量,只需使用临时工具,将要在该上下文中执行的操作作为lambda传递.(需要C++ 0x,当然 - 也可以使用常规函数指针,但它不是那么漂亮.)
像这样:( 编辑恢复异常安全)
class WithPushedMatrix
{
public:
WithPushedMatrix()
{
glPushMatrix();
}
~WithPushedMatrix()
{
glPopMatrix();
}
template <typename Func>
void Execute(Func action)
{
action();
}
};
Run Code Online (Sandbox Code Playgroud)
并像这样使用它:
WithPushedMatrix().Execute([]
{
glBegin(GL_LINES);
//etc. etc.
});
Run Code Online (Sandbox Code Playgroud)
临时对象将设置您的状态,执行操作然后自动将其拆除; 你没有浮动的"松散"状态变量,并且在上下文中执行的动作与它紧密相关.您甚至可以嵌套多个上下文操作,而无需担心析构函数顺序.
您甚至可以更进一步,制作一个通用WithContext类,它需要额外的设置和拆卸功能参数.
编辑:必须将action()调用移动到单独的Execute函数以恢复异常安全 - 如果在构造函数中调用并抛出,则不会调用析构函数.
所以我更多地提出了这个想法,并想出了更好的东西:
我将定义一个With类,它创建上下文变量并将其填充到std::auto_ptr它的初始化器中,然后调用action:
template <typename T>
class With
{
public:
template <typename Func>
With(Func action) : context(new T())
{ action(); }
template <typename Func, typename Arg>
With(Arg arg, Func action) : context(new T(arg))
{ action(); }
private:
const std::auto_ptr<T> context;
};
Run Code Online (Sandbox Code Playgroud)
现在,您可以将它与您最初定义的上下文类型组合:
struct PushedMatrix
{
PushedMatrix() { glPushMatrix(); }
~PushedMatrix() { glPopMatrix(); }
};
Run Code Online (Sandbox Code Playgroud)
并像这样使用它:
With<PushedMatrix>([]
{
glBegin(GL_LINES);
//etc. etc.
});
Run Code Online (Sandbox Code Playgroud)
要么
With<EnabledFlag>(GL_BLEND, []
{
//...
});
Run Code Online (Sandbox Code Playgroud)
优点:
auto_ptrnow 处理,因此如果action抛出,上下文仍将被正确销毁.Execute方法,所以它看起来又干净了!:)With类处理,因此您只需为每种新类型的上下文定义一个简单的ctor/dtor. 一个小问题:正如我上面所写,你需要为ctor声明手动重载,以获得所需数量的参数; 虽然即使只有一个应该涵盖大多数OpenGL用例,但这并不是很好.这应该用可变参数模板整齐地修复 - 只需typename Arg在ctor中替换typename ...Args- 但它将依赖于编译器支持(MSVC2010还没有它们).
sha*_*oth 22
使用这样的对象称为RAII,对于资源管理来说非常典型.是的,有时您会因为忘记提供可变名称而过早销毁临时对象.但是你在这里有一个很大的优势 - 代码变得更安全和更清晰 - 你不必在所有可能的代码路径上手动调用所有清理内容.
一个建议:使用合理的变量名,而不是p.称之为matrixSwitcher或类似的东西,以便读者不认为它是一个无用的变量.
正如其他人所指出的,这是C++中众所周知和鼓励的模式.
处理遗忘变量名的问题的一种方法是定义操作,以便它们需要变量.通过使可能的行动成为RAII类的成员:
PushedMatrix pushed_matrix;;
pushed_matrix.transform( /*...*/ );
Run Code Online (Sandbox Code Playgroud)
或者通过使函数将RAII类作为参数:
PushedMatrix pushed_matrix;
transform_matrix( pushed_matrix, /*...*/ );
Run Code Online (Sandbox Code Playgroud)
我想指出,我的答案实际上包含了有用的信息(更多的是对RAII的模糊引用,显然有19个值得称赞).它不需要c ++ 0x工作,根本不是假设,而是修复了与声明变量需求相关的OP问题.
有一种非常好的方法可以在语法上增强RAII结构(或更精确地说:ScopeGuards):if()语句接受作用于if块的声明:
#include <stdio.h>
class Lock
{
public:
Lock() { printf("locking\n"); }
~Lock() { printf("unlocking\n"); }
operator bool () const { return true;}
};
int main()
{
// id__ is valid in the if-block only
if (Lock id_=Lock()) {
printf("..action\n");
}
}
Run Code Online (Sandbox Code Playgroud)
这打印:
locking
..action
unlocking
Run Code Online (Sandbox Code Playgroud)
如果我们添加一些语法糖,我们可以写
#define WITH(X) if (X with_id_=X())
int main()
{
WITH(Lock) {
printf("..action\n");
WITH(Lock) {
printf("more action\n");
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们使用这样一个事实:只要const引用保持在范围内,用于初始化const引用的temporaries仍然存活,以使其与参数一起工作(我们还修复了WITH(X)接受尾随的麻烦) :
#include <stdio.h>
class ScopeGuard
{
public:
mutable int dummy;
operator bool () const { return false;}
ScopeGuard(){}
private:
ScopeGuard(const ScopeGuard &);
};
class Lock : public ScopeGuard
{
const char *s;
public:
Lock(const char *s_) : s(s_) { printf("locking %s\n",s); }
~Lock() { printf("unlocking %s\n",s); }
};
#define WITH(X) if (const ScopeGuard& with_id_=X) {} else
int main()
{
WITH(Lock("door")) {
printf("..action\n");
WITH(Lock("gate")) {
printf("more action\n");
}
}
}
Run Code Online (Sandbox Code Playgroud)
TATA!
这种方法的一个很好的副作用是,所有"受保护的"区域都是通过WITH(...) {...}模式统一识别的- 这是代码评论等的一个很好的属性.