紫月城游戏软件

低调做技术

最近的两个问题:less for std::map,静态变量初始化顺序


说下最近自己遇到的两个值得让人注意的问题。
其一是关于自己给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编译器自己?

 

 

 


 

posted on 2008-11-11 17:55 Kevin Lynx 阅读(1590) 评论(8)  编辑 收藏 引用 所属分类: C++

评论

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2008-11-11 19:04 空明流转

没有保证初始化顺序,要用一些模式手工初始化。  回复  更多评论   

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2008-11-11 19:42 啸天猪

第一个问题:STL要求predicator应该是纯函数性质,不能有状态变化;估计是你的实现为了强制这一点,在模板内部做了些手脚吧

第二个问题:同一TU内,non-local static varible按照定义的顺序被初始化,这是标准所规定的  回复  更多评论   

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2008-11-11 19:57 Xw.Y

1. 没想法

2. 全局的静态变量顺序没有保证。偶也吃过苦头,查文档无果。
通常偶都是在main起来后重新初始化静态变量。申明用指针而不用实例。
你的例子太复杂了,
我印象中这样就有问题(不过我也可能不正确,这种太容易忘记了)
//somefile.cpp
static bool gs_initialized = false;

class A{
public:
A(void) { gs_initialized = true; }
};

A InstanceA;

int main(void){
// gs_initialized true/false不确定
}

问下楼上的,TU是指什么?  回复  更多评论   

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2008-11-12 09:07 Kevin Lynx

@啸天猪
STL predicator不会要求是纯虚函数性质,唯一的要求就是这是一个具有operator()性质的东西,普通C函数,重载了operator() 的类均可。我文章里说的问题在于,函数不是:
bool operator() ( .... ) const // 需要加上const
{
}

TU是不是编译单元?如果是标准规定,哥们可以给我下文档链接不?

@Xw.Y

我的问题同你的本质是一样的。

  回复  更多评论   

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2008-11-12 09:54 浪迹天涯

感觉有点印象,后来翻翻EffectiveC++.chm,杂项->条款47:

对于不同被编译单元中的非局部静态对象,你一定不希望自己的程序行为依赖于它们的初始化顺序,因为你无法控制这种顺序。让我再重复一遍:你绝对无法控制不同被编译单元中非局部静态对象的初始化顺序。

很自然地想知道,为什么无法控制?

这是因为,确定非局部静态对象初始化的 " 正确" 顺序很困难,非常困难,极其困难。即使在它最普通的形式下 ---- 多个被编译单元,多个通过隐式模板实例化所生成的非局部静态对象(隐式模板实例化时,它们本身可能都会产生这样的问题) ---- 不仅不可能确定正确的初始化顺序,往往连找一个可以确定正确顺序的特殊情况都不值得。

在 "混沌理论" 领域,有一个原理称为 "蝴蝶效应" 。这条原理声称,世界某个角落的一只蝴蝶拍动翅膀,会对大气产生微小的影响,从而导致某个遥远的地方天气模式的深刻变化。稍微准确一点来说也就是:对于某种系统,输入的微小干扰会导致输出彻底的变化。

软件系统的开发也表现了自身的 "蝴蝶效应"。一些系统对需求的细节高度敏感,需求发生细小的变化,实现系统的难易程度就会发生巨大的变化。例如,条款29说明,将一个隐式转换的要求从 "String到char*" 改为 "String到const char*",就可以将一个运行慢、容易出错的函数用一个运行快并且安全的函数来代替。

确保非局部静态对象在使用前被初始化的问题也和上面一样,它对你的实现细节十分敏感。但是,如果你不强求一定要访问 "非局部静态对象",而愿意访问具有和非局部静态对象 "相似行为" 的对象(不存在初始化问题),难题就消失了。取而代之的是一个很容易解决的问题,甚至称不上是一个问题。

这种技术 ---- 有时称为 "单一模式"(译注:即Singleton pattern,参见 "Design Patterns" 一书)---- 本身很简单。首先,把每个非局部静态对象转移到函数中,声明它为static。其次,让函数返回这个对象的引用。这样,用户将通过函数调用来指明对象。换句话说,用函数内部的static对象取代了非局部静态对象。(参见条款M26)

这个方法基于这样的事实:虽然关于 "非局部" 静态对象什么时候被初始化,C++几乎没有做过说明;但对于函数中的静态对象(即,"局部" 静态对象)什么时候被初始化,C++却明确指出:它们在函数调用过程中初次碰到对象的定义时被初始化。所以,如果你不对非局部静态对象直接访问,而用返回局部静态对象引用的函数调用来代替,就能保证从函数得到的引用指向的是被初始化了的对象。这样做的另一个好处是,如果这个模拟非局部静态对象的函数从没有被调用,也就永远不会带来对象构造和销毁的开销;而对于非局部静态对象来说就没有这样的好事。

  回复  更多评论   

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2008-11-12 13:33 啸天猪

1 我的意思是predicatory应该像数学函数那样,不具备状态变化,而不是pure virtual 这个意思

你的STL实现为了强制这个语意,总是使用const predicator object,这样就只能调用operator () const,强制predicator在被使用时无法发生状态变化。

STL对predicator的这个要求是语意上的,而不是语法上的;Effective STL上解释的挺好的

http://stl.winterxy.com/html/item_39.html


2 TU就是编译单元

参见 "C++ standard 2003: 3.6.2 Initialization of non-local objects"

http://d.download.csdn.net/source/216275
  回复  更多评论   

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2008-11-12 13:45 luke

对于静态变量的初始化顺序问题,在thinking in java 的name control 一章里介绍了两个技巧来出来处理这类问题。  回复  更多评论   

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2008-11-12 13:47 luke

错了,是thinking in c++  回复  更多评论   


标题  
姓名  
主页
验证码 *
内容(提交失败后,可以通过“恢复上次提交”恢复刚刚提交的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
[使用Ctrl+Enter键可以直接提交]

相关链接:
网站导航: