AutoRunner:一个模板类,用于在代码块中自动运行启动和清理代码






4.78/5 (20投票s)
2003 年 3 月 28 日
5分钟阅读

75063

373
所提出的模板类提供了一种简便的方法来创建简单的类,这些类能够处理代码块中“逻辑”的初始化和清理。
引言
有时您希望在代码块的末尾自动执行某些代码,以释放内存、释放资源、解锁文件等。C++ 标准、STL 或您的 C++ 编译器已经预见了某些标准的清理方法。例如,自动指针(或智能指针)提供了一种确保您的内存将在代码块结束时被释放的方法。
自动清理是如何实现的
自动清理是通过一个单独的类来实现的,该类的析构函数会自动执行必要的清理。例如,以下代码展示了一个执行文件锁定的类的定义。构造函数将自动锁定文件。析构函数将解锁它。
class FileLock { public: FileLock (char *FileName); FileLock (char *FileName, long Timeout); ~FileLock (); private: HANDLE m_Handle; };
使用这样的类非常简单。要锁定文件,请创建一个该类的实例;要解锁它,请销毁该实例。
void myFunction () { FileLock fileLock("myfile.txt"); ... // file is automatically unlocked if fileLock goes out of scope }
如果清理不是在析构函数中执行的呢?
假设我们有一个类,它(除了许多其他方法之外)包含以下两个方法:increment
和 decrement
。如果一个函数或方法调用 increment()
,那么它也负责在最后调用 decrement
,就像本示例所示
void myFunction (MyClass &myClass)
{
myClass.increment();
...
myClass.decrement();
}
现在,如果函数忘记在函数末尾调用 decrement
怎么办?或者如果函数中抛出了异常怎么办?是的,MyClass
可能会失控,应用程序可能会显示不正确的数据,可能会崩溃……解决方案很简单,但很繁琐。只需编写一个类,该类的构造函数将调用 increment
,析构函数将调用 decrement
,如下所示
class MyClassIncrementer { public: MyClassIncrementer(MyClass &myClass) : m_myClass(myClass) {m_myClass.increment();} ~MyClassIncrementer() {m_myClass.decrement();} private: MyClass &m_myClass; };
现在,我们无需自己调用 increment
和 decrement
,只需使用这个新类即可
void myFunction (MyClass &myClass)
{
MyClassIncrementer myClassIncrementer(myClass);
...
}
在函数结束时,myClassIncrementer
将自动销毁,这将触发 myClass.decrement()
。
我应该一遍又一遍地编写这样的新类吗?
不,通过使用模板,我们可以轻松地将此功能隐藏在模板中。
静态方法或全局函数
首先,我们编写一个简单的模板,它将处理全局函数或静态方法的自动调用。实现如下
template <void (*SF)(), void (*EF)()> class AutoRunnerStatic { public: AutoRunnerStatic () { SF();} ~AutoRunnerStatic () { EF();} };
模板将传递两个函数指针。第一个(StartFunction
)将在构造函数中调用。第二个(EndFunction
)将在析构函数中调用。
假设我们有以下全局函数
void FirstAction() { std::cout << " FirstAction" << std::endl;} void LastAction () { std::cout << " LastAction" << std::endl;}
想象一下,这些函数在这里做了一些非常有用的事情。由于这是一个示例,它们只会打印一些输出。
假设我们规定,如果在例程中调用 FirstAction
,那么 EndAction
也应该在例程的末尾调用。为了简化在应用程序中的使用,我们使用 typedef
创建了一个快捷方式。
typedef AutoRunnerStatic<FirstAction,LastAction> AutoAction;
现在应用程序已准备好使用“AutoAction”,如下所示
int main() { std::cout << "In beginning of main" << std::endl; { AutoAction autoAction; std::cout << " In sub block" << std::endl; } std::cout << "At end of main" << std::endl; return 0; }
将打印以下输出
In beginning of main
FirstAction
In sub block
LastAction
At end of main
好的,这解决了全局函数的问题,那么非静态方法呢?
非静态方法
要为非静态方法实现相同效果的模板会有点复杂。这还需要 Visual C/C++ 7(可能还有 7.1)或 GNU C++(在 Visual C/C++ 6 中无法编译!!!)。
template <class T, void (T::*SF)(), void (T::*EF)()> class AutoRunner { public: AutoRunner (T &instance) : m_instance(instance) { (m_instance.*SF)();} ~AutoRunner () { (m_instance.*EF)();} private: T &m_instance; };
在这里,方法 SF
和 EF
分别在模板类的构造函数和析构函数中自动调用。
此模板中的真正挑战是正确地放置所有括号、星号和圆括号(这并不简单)。
假设我们有以下类,其中包含 FirstMethod
和 SecondMethod
方法。与全局函数类似,我们规定调用 FirstMethod
的调用者也应调用 LastMethod
。
class MyClass { public: MyClass (long value) : m_value(value) {} void FirstMethod() { std::cout << " FirstMethod, value is " << m_value << std::endl; } void LastMethod () { std::cout << " LastMethod , value is " << m_value << std::endl; } private: long m_value; };
MyClass
的编写者还预见了以下 typedef
,以便用户更容易使用该方法
typedef AutoRunner<MyClass,&MyClass::FirstMethod,
&MyClass::LastMethod> MyClassAuto;
现在,MyClassAuto
类可以轻松用于确保在执行 FirstMethod
时调用 LastMethod
,如下所示。
int main() { std::cout << "In beginning of main" << std::endl; MyClass myInstance(1); MyClass myInstance2(2); { MyClassAuto myClassAuto(myInstance); std::cout << " In sub block" << std::endl; } { MyClassAuto myClassAuto(myInstance2); std::cout << " In sub block" << std::endl; } std::cout << "At end of main" << std::endl; return 0; }
这将打印以下输出
In beginning of main
FirstMethod, value is 1
In sub block
LastMethod , value is 1
FirstMethod, value is 2
In sub block
LastMethod , value is 2
At end of main
这就证明了 FirstMethod
和 LastMethod
被正确调用。
const 方法
在编写了这两个精美的模板后,我立即开始在一个我想编写的新类中使用它们。我的类有两个方法 - lock()
和 unlock()
- 用于锁定和解锁数据结构(用于多线程环境)。我的类名为 ThreadSafeList
(以后可能会有更多关于此的文章)。
我定义了一个 Locker
类,如下所示
typedef AutoRunner<ThreadSafeList,lock,unlock> Locker;
然后,我的方法只需创建 *this
上的 Locker
,如下所示
void clear () {Locker locker(*this); stltype::clear();}
确实,这工作正常……直到我编写了一个 const
方法,如下所示
bool empty () const {Locker locker(*this); return stltype::empty();}
编译器向我抛出了以下消息
error C2440: 'specialization':
cannot convert from 'void (__thiscall ThreadSafeList<T>::* )(void) const'
to 'void (__thiscall ThreadSafeList<T>::* )(void)'
AutoRunner
模板显然不喜欢 const
实例。然而,解决方案非常简单。我们编写一个模板的 const
版本,如下所示
template <class T, void (T::*SF)() const, void (T::*EF)() const> class AutoRunnerConst { public: AutoRunnerConst (const T &instance) : m_instance(instance) { (m_instance.*SF)();} ~AutoRunnerConst () { (m_instance.*EF)();} private: const T &m_instance; };
我们只是在模板中将许多内容设为 const
。两个方法、私有数据成员以及构造函数的参数都设为 const
。我们只需要将我们的 typedef
修改为
typedef AutoRunnerConst<ThreadSafeList,lock,unlock> Locker;
我们就准备好了。嗯,不完全是,我们仍然会收到以下编译器错误
error C2664: 'EnterCriticalSection' :
cannot convert parameter 1 from 'const CRITICAL_SECTION *'
to 'LPCRITICAL_SECTION'
Conversion loses qualifiers
while compiling class-template member function
'void ThreadSafeList<T>::lock(void) const'
此消息意味着 lock 方法试图修改管理锁定的数据成员(在本例中为 CRITICAL_SECTION
),而在 const
方法中不允许这样做。
所以我们需要采取的最后一步是使我们的关键部分可变,如下所示
mutable CRITICAL_SECTION m_criticalSection;
瞧,它奏效了。
缺点和改进
最大的缺点是传递给模板的方法不能有参数。这可能可以通过添加更多的模板类来解决,但我不想深入研究。
唉,第二个和第三个模板在 Microsoft 的 Visual C/C++ 6.0 中无法编译,尽管它们在 Visual C/C++ 7.1 中编译正确(不确定 7.0)。这表明 7.1 将是一个比 6.0 好得多的编译器。
另一个改进可能是将应该成对调用的方法(在我示例中的 FirstMethod
和 LastMethod
)设为类中的 private
,并将模板类设为其友元。这可以确保没有人会显式调用 FirstMethod
和/或 LastMethod
,从而存在未能正确将它们作为一对执行的风险。
您喜欢模板吗?
本文再次展示了模板如何用于解决需要一遍又一遍地编写同一种类的问题。只需编写模板(一次!),然后使用它(或 typedef
它以方便调用者)。
历史
- 2003 年 3 月 28 日:原始版本
- 2003 年 6 月 20 日:添加了
AutoRunnerConst