跳转到内容

更多 C++ 习语/巧妙计数器

来自维基教科书,开放的书籍,为开放的世界

巧妙计数器

[编辑 | 编辑源代码]

确保非局部静态对象在首次使用之前初始化,并且仅在最后使用该对象后销毁。

也称为

[编辑 | 编辑源代码]

Schwarz 计数器

当静态对象使用其他静态对象时,初始化问题变得更加复杂。如果静态对象具有非平凡的初始化,则必须在使用之前对其进行初始化。跨编译单元的静态对象的初始化顺序没有明确定义。多个静态对象(分布在多个编译单元中)可能正在使用单个静态对象。因此,必须在使用之前对其进行初始化。一个例子是std::cout,它通常被其他几个静态对象使用。

解决方案和示例代码

[编辑 | 编辑源代码]

“巧妙计数器”或“Schwarz 计数器”习语是将引用计数习语应用于静态对象初始化的一个例子。

// Stream.h
#ifndef STREAM_H
#define STREAM_H

struct Stream {
  Stream ();
  ~Stream ();
};
extern Stream& stream; // global stream object

static struct StreamInitializer {
  StreamInitializer ();
  ~StreamInitializer ();
} streamInitializer; // static initializer for every translation unit

#endif // STREAM_H
// Stream.cpp
#include "Stream.h"

#include <new>         // placement new
#include <type_traits> // aligned_storage

static int nifty_counter; // zero initialized at load time
static typename std::aligned_storage<sizeof (Stream), alignof (Stream)>::type
  stream_buf; // memory for the stream object
Stream& stream = reinterpret_cast<Stream&> (stream_buf);

Stream::Stream ()
{
  // initialize things
}
Stream::~Stream ()
{
  // clean-up
} 

StreamInitializer::StreamInitializer ()
{
  if (nifty_counter++ == 0) new (&stream) Stream (); // placement new
}
StreamInitializer::~StreamInitializer ()
{
  if (--nifty_counter == 0) stream.~Stream ();
}

Stream 类的头文件必须在对Stream 对象调用任何成员函数之前包含。在每个编译单元中都包含了StreamInitializer 类的实例。对Stream 对象的任何使用都在包含头文件之后,这确保了在使用Stream 对象之前调用了初始化器对象的构造函数。

Stream 类的头文件声明了对Stream 对象的引用。此外,该引用是extern,这意味着它在一个翻译单元中定义,并且对它的访问是由链接器而不是编译器解析的。
Stream 类的实现文件最终定义了Stream 对象,但使用了一种不寻常的方式:它首先定义了一个静态(即特定于翻译单元)缓冲区。该缓冲区既正确对齐又足够大以存储类型为Stream 的对象。然后,将头文件中定义的Stream 对象的引用设置为指向该缓冲区。
这种缓冲区变通方法使我们可以精细地控制何时调用Stream 对象的构造函数和析构函数。在上面的示例中,构造函数是在第一个StreamInitializer 对象的构造函数中调用的,使用 placement new 将其放置在缓冲区中。Stream 对象的析构函数是在最后一个StreamInitializer 对象被销毁时调用的。

这种变通方法是必要的,因为在 Stream.cpp 中定义一个Stream 变量(无论是静态的还是非静态的) - 都会在StreamInitializer 之后定义它,而StreamInitializer 是通过包含头文件来定义的。然后,StreamInitializer 的构造函数将在Stream 的构造函数之前运行,更糟糕的是,初始化器的析构函数将在Stream 对象的析构函数之后运行。上面的缓冲区解决方案避免了这种情况。

已知用途

[编辑 | 编辑源代码]

标准 C++ <iostream>std::coutstd::cinstd::cerrstd::clog

[编辑 | 编辑源代码]

参考文献

[编辑 | 编辑源代码]
华夏公益教科书