loop_in_codes

低调做技术__欢迎移步我的独立博客 codemaro.com 微博 kevinlynx

最近的两个问题: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 阅读(7384) 评论(13)  编辑 收藏 引用 所属分类: c/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++  回复  更多评论   

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2009-06-04 17:56 adie

1. "另一方面,就STL内部的风格来看,应该不会把predicator处理
成const &之类的东西,全部是value形式的。奇怪了。"

STL 内部就是 const & 的,对模版类型参数拷贝代价是未知的,而且拷贝构造函数是否实现都未知,必须是 const& 的. VC8 的 std::less:

template<class _Ty>
struct less
: public binary_function<_Ty, _Ty, bool>
{ // functor for operator<
bool operator()(const _Ty& _Left, const _Ty& _Right) const
{ // apply operator< to operands
return (_Left < _Right);
}
};

2. 不同编译单元的静态变量初始化顺序不确定是因为链接器的顺序无法保证,和写的 makefile 有关. 但在同一个编译单元的静态变量初始化顺序是可以确定的。
  回复  更多评论   

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2009-06-04 20:28 Kevin Lynx

@adie
1.我这里说的predicator,指的是less本身。包括传给find_if的functor,都被我称为predicator。STL内部保存这种predicator,都是以value的形式保存。既然是value的形式,就不会存在调用这个predicator的operator()必须要求其为bool (*)() const类型的。以前没搞清楚这个问题,现在也没关注过。
你举的例子中,谈的是这个predicator的bool operator()( ...)成员函数的参数为const&。对于less而言,它的这个operator()的参数是map内部保存的element。无论是从效率还是其他方面,是肯定要const&的。

2.初始化顺序这个问题,我对链接器细节没怎么关注过。不过,情况可能真如你所说。谢过。

  回复  更多评论   

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2009-06-05 10:29 adie

呵呵,因为 predicator 是个模板参数,不存在 const& 的形式,所以理解成了它的参数了。
从语义上说,确如啸天猪所言,要求它为 const 虽然不能保证就满足要求,的确也是合理的。
从语法上来说,虽然这个比较对象是以 value 的形式保存的,但是使用这个比较对象的函数是 const 的,因此他得到的 this 指针就是 const 的,它的成员 predicator 对象也就是 const 的了。
至于 Debug 可以编译通过确实是由于 Debug 和 Release 版的代码不一样,Release 比较的时候用的:
#define _DEBUG_LT_PRED(pred, x, y) pred(x, y)
Debug 版比较的时候用的:
#define _DEBUG_LT_PRED(pred, x, y) _Debug_lt_pred(pred, x, y, __FILEW__, __LINE__)
可以看到 Debug 版本比较的时候用了一个函数,比较对象作为函数参数传入的时候进行了一次拷贝,拷贝后的对象没有 const 了。  回复  更多评论   

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2009-06-05 10:36 Kevin Lynx

@adie
刚正在看vc2005中的xtree找原因。在_Lbound中,确实是因为_DEBUG_LT_PRED导致DEBUG和RELEASE使用了不同的代码。而且确实是因为_Lbound是const的才导致this->comp也是const的。但是,在看到你评论之前我还没反应过来:因为this->comp通过作为函数参数而去掉了const修饰,囧。
起码真相大白了。3Q
  回复  更多评论   

# re: 最近的两个问题:less for std::map,静态变量初始化顺序 2011-09-21 17:05 pop

关于ObjMgr.obj和main.obj里都有一个文件静态变量_t的问题,我想说,既然都是全局的了,为什么还要static呢,难道全局+静态这样定义变量_t不会觉得有重复的感觉吗(静态其实也是为了数据共享);,是不是静态的全局变量应该不提倡这种写法呢;
另外,对于普通的全局变量定义,都会写在.cpp内,然后.h是extern它,还得加上#param once防止重复包含,才是标准写法不是?否则,不出现多个_t才怪~  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理