std::ios_base::register_callback 的索引参数有什么作用?

What does the index parameter of std::ios_base::register_callback do?

本文关键字:参数 什么 作用 索引 callback ios base register std      更新时间:2023-10-16

参见 http://www.cplusplus.com/reference/ios/ios_base/register_callback/

我以为index会控制触发回调的顺序,但事实似乎并非如此。此参数有什么作用?

下面是一个代码片段:

5 void testfn (std::ios::event ev, std::ios_base& stream, int index)
6 {
7   switch (ev)
8   {
9     case stream.copyfmt_event:
10       std::cout << "copyfmt_eventn"; break;
11     case stream.imbue_event:
12       std::cout << "imbue_eventn"; break;
13     case stream.erase_event:
14       std::cout << "erase_eventn"; break;
15   }
16 }
17
18 void testfn2 (std::ios::event ev, std::ios_base& stream, int index)
19 {
20   switch (ev)
21   {
22     case stream.copyfmt_event:
23       std::cout << "copyfmt_event 2n"; break;
24     case stream.imbue_event:
25       std::cout << "imbue_event 2n"; break;
26     case stream.erase_event:
27       std::cout << "erase_event 2n"; break;
28   }
29 }
30
31 int main () {
32   std::ofstream filestr;
33   filestr.register_callback (testfn,0);
34   filestr.register_callback (testfn2,1);
35   filestr.imbue (std::cout.getloc());
36
37   std::ofstream filestr2;
38   filestr2.register_callback (testfn,1);
39   filestr2.register_callback (testfn2,0);
40   filestr2.imbue (std::cout.getloc());
41   return 0;
42 }

下面是输出:

jefferson@ubuntu:~/inheritance$ g++ -std=c++14 stream_callbacks.cpp
jefferson@ubuntu:~/inheritance$ ./a.out
imbue_event 2
imbue_event
imbue_event 2
imbue_event
erase_event 2
erase_event
erase_event 2
erase_event

我有一个利用它的库(libaddr)。

输出地址向量时,我的代码将默认使用逗号 (",") 作为默认分隔符:

addr::addr::vector_t addresses;
...define the addresses somehow...
std::cout << addresses;  // defaults to commas between each address

但是,您可能需要一个空格或换行符:

std::cout << addr::setaddrsep(" ") << addresses;
std::cout << addr::setaddrsep("n") << addresses;

在这里,我展示了如何使用结构和函数来更改分隔符,如上所示(大部分源代码都在 addr.h 中,请参阅 addr.cpp 了解xalloc()调用):

struct _setaddrsep
{
std::string     f_sep;
};
template<typename _CharT, typename _Traits>
inline std::basic_ostream<_CharT, _Traits> &
operator << (std::basic_ostream<_CharT, _Traits> & out, _setaddrsep sep)
{
int const index(get_ostream_index());
_ostream_info * info(static_cast<_ostream_info *>(out.pword(index)));
if(info == nullptr)
{
info = new _ostream_info;
out.pword(index) = info;
out.register_callback(basic_stream_event_callback, index);
}
info->f_sep = sep.f_sep;
return out;
}

正如我们所看到的,我有一个调用get_ostream_index()它返回您提到的索引。以下函数为我的libaddr环境调用一次xalloc()函数。之后,索引在整个程序运行过程中保持不变。换句话说,此索引由ios_base库分配给您的类。

int get_ostream_index()
{
cppthread::guard lock(*cppthread::g_system_mutex);
if(!g_ostream_index_allocated)
{
g_ostream_index_allocated = true;
g_ostream_index = std::ios_base::xalloc();
}
return g_ostream_index;
}

警告:您可能想编写int g_ostream_index = xalloc();来直接初始化全局变量。这很危险,至少有两个原因:(1)你不能100%确定xalloc()可以这么早被称为(虽然可能);(2) 如果您有另一个使用输出流的全局函数,则g_ostream_index可能尚未初始化(即,C++您无法控制全局初始化的顺序)。使用函数,您可以确保它按顺序发生。此外,如果您编写 .so 或 .DLL,初始化将在您自己的程序初始化之前进行,因此只要库本身不包含另一个尝试使用该g_ostream_index的全局,您就是安全的。就个人而言,我宁愿安全也不愿后悔。这些错误可能很难调试,因为它发生在调用main()之前。

稍后打印地址时,它可以检查是否找到了用户指定的参数:

template<typename _CharT, typename _Traits, typename _ContainerT>
inline typename std::enable_if<
std::is_same<_ContainerT, addr::vector_t>::value
|| std::is_same<_ContainerT, addr::set_t>::value
, std::basic_ostream<_CharT, _Traits>>::type &
operator << (std::basic_ostream<_CharT, _Traits> & out, _ContainerT const & addresses)
{
std::string sep(",");
_ostream_info * info(static_cast<_ostream_info *>(out.pword(get_ostream_index())));
if(info != nullptr)
{
sep = info->f_sep;
}
bool first(true);
for(auto const & a : addresses)
{
if(first)
{
first = false;
}
else
{
out << sep;
}
out << a;
}
return out;
}

如我们所见,我将一个名为sep的变量设置为默认分隔符","。然后我检查out.pword(index),如果不nullptr,则用用户定义的分隔符替换sep

这是我的_ostream_info结构,其中包括两个参数:

struct _ostream_info
{
addr::string_ip_t   f_mode = addr::string_ip_t::STRING_IP_ALL;
std::string         f_sep = std::string(",");
};

我认为 99.9% 的情况下,您应该使用指向结构的指针,这样您就可以随着时间的推移添加任意数量的参数,而无需编辑所有流函数以从long转到指针(即您始终可以在结构中有一个long开始)。

另一个重要点:如果使用pword()功能,回调非常重要,因为在这种情况下,您分配的内存必须在回调收到std::ios_base::erase_event事件时释放。

还必须实现copy_fmt以确保它复制缓冲区(或使用某种引用计数)。


当然,在您的示例中,索引为 0 和索引 1 的回调不是您的。这两个调用可能会破坏系统定义的回调(即每个索引肯定只有一个回调)。由于我的xalloc()返回 2,因此我想 0 和 1 已经是系统定义的,这当然是您的调用似乎有效的原因。

相关文章: