说下最近自己遇到的两个值得让人注意的问题。
其一是关于自己给std::map写less predicate,std::map第三个参数是一个典型的functor。map内部将使用
这个functor去判定两个元素是否相等,默认使用的是std::less。但是为什么传入的是一个判断第一个参数
小于第二个参数的functor,而不是一个判断两个参数是否相等的functor?按照STL文档的说法,当检查两
个参数没有小于也没有大于的关系时,就表示两个参数相等。不管怎样,我遇到了需要自己写这个functor
的需求,于是:
struct MyLess
{
bool operator() ( long left, long right )
{
//...
}
};
DEBUG模式下编译没问题,RELEASE模式下却出现C3848的错误。这就有点奇怪了,如果确实存在语法错误,
那么DEBUG和RELASE应该一样才对。查了下MSDN,C3848的错误是因为const限定符造成的,如:
const MyLess pr;
pr(); // C3848
于是,在operator()后加上const,就OK了。看了下VC std::map相关代码,以为是DEBUG和RELEASE使用了不
同的代码造成。但是我始终没找到不同点。另一方面,就STL内部的风格来看,应该不会把predicator处理
成const &之类的东西,全部是value形式的。奇怪了。
第二个问题,涉及到静态变量。这个东西给我的印象特别深刻,因为以前去一家外企应聘时被问到,当时
觉得那个LEADER特别厉害。回来后让我反思,是不是过多地关注了C++里的花哨,而漏掉了C里的朴素?导致
我至今对纯C存在偏好。
说正题,我现在有如下的文件关系:
// def.h
struct Obj
{
Obj()
{
ObjMgr::AddObj( id, this );
}
int id;
};
struct ObjMgr
{
static void AddObj( int id, Obj *t )
{
ObjTable[id] = t;
}
static std::map<int, Obj*> ObjTable;
};
static Obj _t;
// ObjMgr.cpp
#include "def.h"
static std::map<int, Obj*>::ObjMgr ObjTable;
// main.cpp
#include "def.h"
这里举的例子可能有点不恰当,我在一台没有编译器的机器上写的这篇文章。忽略掉这些旁支末节。我的意思,
就是想让Obj自己自动向ObjMgr里添加自己。我们都知道静态变量将在程序启动时被初始化,先于main执行之前。
上面代码有两个问题:
一、
代码没有按照我预期地执行,如果你按照我的意思做个测试,你的程序甚至在进main之前就crash了。我假定你
用的是VC,因为我没在其他编译器上试验过。问题就在于,Obj的构造依赖于ObjTable这个map对象。在调试过程
中我发现,虽然ObjTable拥有了内存空间,其this指针有效,但是,map对象并没有得到构造。我的意思是,Obj
的构造先于ObjTable构造(下几个断点即可轻易发现),那么在执行map::operator[]时,就出错了,因为这个时候
map里相关数据还没准备好。
那是否存在某种机制可以手动静态变量的初始化顺序呢?不知道。我最后怎样解决这个问题的?
二、
在还没有想到解决办法之前(不改变我的设计),我发现了这段代码的另一个问题:我在头文件里定义了静态
变量:static Obj _t; 这有什么问题?想想预编译这个过程即可知道,头文件在预编译阶段被文本展开到CPP
文件里,然后,ObjMgr.cpp和main.cpp文件里都将出现static Obj _t;代码。也就是说,ObjMgr.obj和main.obj
里都有一个文件静态变量_t。
看来,在头文件里放这个静态变量是肯定不对的。于是,我将_t移动到ObjMgr.cpp里:
// ObjMgr.cpp
#include "def.h"
static std::map<int, Obj*>::ObjMgr ObjTable;
static Obj _t;
按照这样的顺序定义后,_t的构造居然晚于ObjTable了。也就是说,放置于前面的变量定义,就意味着它将被
首先构造初始化。这样两个问题都解决了。
但是,谁能保证这一点特性?C标准文档里?还是VC编译器自己?