天秤座的唐风

总会有一个人需要你的分享~!- 唐风 -

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  13 随笔 :: 0 文章 :: 69 评论 :: 0 Trackbacks

这两天写代码,遇到这么一个模板决议的问题:
    有一个类 A,其中有两个数据成员(方便起见都是 public )。为了从其它部分(比如文件)读入数据,我为 A 类重载了输入流操作符。代码如下所示:
程序1

typedef struct tagA {
    UINT32  biSize;
    UINT32  biCount;
}
 A;

template 
<typename DataStream>
DataStream
& operator>>(DataStream& a_data, A& a_fileHeader)
{        
    a_data
>>a_fileHeader.biSize>>a_fileHeader.biCount;

    
return a_data;        
}


    一般使用标准IO流的话,这样做至少在编译上是没有问题的(读二进制文件不行,后有补充)。
    在我的程序中,我目前只是测试时用文件读取,到后期,肯定会换成其它形式(可能是直接大块类似的数据整在一起,放在内存中之类),所以我打算为数据源写一个包装类,在目前的阶段,直接转到标准IO文件读取,后期再修改数据源类。因为使用数据源类需要保持与标准IO的写法一致( cin>>xx ),所以这个类要为每种基本类型重载输入流操作符。这些重载的代码结构都是一样的,只是数据类型不同,因此用模板是最好的,所以一开始,我写成了下面这个样子:
程序2

class DataFromFile
{
public:
    DataFromFile(CHAR 
const *a_fileName)
        :dataStream(a_fileName, std::ios::binary
|std::ios::in)
    
{ assert(dataStream.good());}
    
~DataFromFile() 
    
{ dataStream.close();}
public:
    template 
<typename T>
    inline DataFromFile
& operator>> (T& a_data)        
    
{                                                  
        dataStream.read((CHAR 
*)&a_data, sizeof(T));   
        
return *this;                                     
    }

private:
    std::fstream dataStream;
}
;


    但在实际使用时,像下面的语句,就发生了编译问题:

DataFromFile source("11.bin");
A dest;
source
>>dest; 


    编译器对这段代码抱怨,说无法确定使用哪个模板……
    当然,我是希望编译器在这里使用程序一中的非成员模板函数,然后在那个模板函数中,再用程序2中的成员模板函数来实例化基本类型的输入操作。可是编译器没有这么智能,我理解,这里用A做模板参数的话,确实有二义性。
    想来想去没想到好的办法消除这种二义性,只好修改代码,去掉其中一个的模板定义。类比地思考一下,标准IO并没有发生这个问题,因为标准IO的输入流操作并不是模板函数,而这里的DataFromFile类,明显使用模板会比较优雅,但二义性的问题不知道怎么解决。最后没办法,改成用宏来实现,代码如下:
程序3

#define InputForBuildIn(Type)                         \
inline DataFromFile
& operator>> (Type& a_data)        \
{                                                     \
    dataStream.read((CHAR 
*)&a_data, sizeof(Type));   \
    
return *this;                                     \
}


class DataFromFile
{
public:
    DataFromFile(CHAR 
const *a_fileName)
        :dataStream(a_fileName, std::ios::binary
|std::ios::in)
    
{ assert(dataStream.good());}
    
~DataFromFile() 
    
{ dataStream.close();}
public:
    InputForBuildIn(INT8)
    InputForBuildIn(UINT8)
    InputForBuildIn(INT16)
    InputForBuildIn(UINT16)
    InputForBuildIn(INT32)
    InputForBuildIn(UINT32)
private:
    std::fstream dataStream;
}
;


    问题是没有了,但总觉得不是滋味。(不知道有没有更好的解决办法?哪位大侠指点下?)


PS:
    对 C++ 的标准 IO 流还不熟,在以二进制方式打开文件时,使用 >> 操作符无法读入数据?一开始的时候调了很久,后来才发现这里可能有问题,转成使用 .read() 方法来读数据,就成功了。标准 IO 还是得好好再补补啊。

posted on 2009-06-28 12:01 唐风 阅读(722) 评论(7)  编辑 收藏 引用 所属分类: 语言技术

评论

# re: 输入流函数模板决议问题导致程序不简洁 2009-11-15 03:54 OwnWaterloo
没办法…… 让它吃点亏好了:
template <typename DataStream>
DataStream& operator>>(DataStream& a_data, A& a_fileHeader);

该成这样:
template<typename C,class T>
std::basic_istream<C,T>& operator>>(std::basic_istream<C,T>& s,A& a);

这也是为iostream写扩展时要考虑的一个问题:
1. 要么严格使用标准库中的(w)i(f/string)stream,basic_i(f/string)stream
2. 要么放宽,使用任意重载了>>的。



二进制和文本关系到是否转换换行模式。

写入文件时是怎么写的?
>>和<<是格式化输出。
read/write是非格式化输出。
12
<<结果就是 '1','2' , >>时也希望是这样
write结果就是 0c,00,00,00, read时也希望是这样。

  回复  更多评论
  

# re: 输入流函数模板决议问题导致程序不简洁 2009-11-21 12:53 唐风
居然发现 SFINAE 正是我要的解决方案~!
后来试了一下下面这段实现,就 OK 了……
 
class DataFromFile
{
public:
    DataFromFile(
char const *a_fileName)
        :dataStream(a_fileName, std::ios::binary
|std::ios::in)
    
{ assert(dataStream.good());}
    
~DataFromFile() 
    
{ dataStream.close();}
public:
    template 
<typename T> inline
    typename boost::disable_if
<boost::is_class<T>, DataFromFile&>::type 
    
operator>> (T& a_data)        
    
{                                                  
        dataStream.read((
char *)&a_data, sizeof(T));   
        
return *this;                                     
    }

private:
    std::fstream dataStream;
}
;
 
必须要赞一下 boost 的 enable_if ,确实是应用 SFINAE 相当优雅的一种方式~!
 
你的回复中对于输入输出流的解释,我还是不太理解,呵呵,要学的东西真多哇,C++ 的 IO 流和 IO 流的本地化是我比较“恐惧”的部分。有没有什么好库可以代替?
对于学习这块,你有什么好的推荐?
 
 
  回复  更多评论
  

# re: 输入流函数模板决议问题导致程序不简洁 2009-11-21 14:30 OwnWaterloo
@唐风
这方法好,学习了~

我觉得《the c++ programming language》中21章将流的架构讲得很清楚。
具体实现么。。。
有2本书可能有这方面的内容,我没细看……
1本是《C++ Templates》
1本是《标准C++输入输出流与本地化》
http://www.china-pub.com/3274


代替io流么…… 有个iconv可以做编码转换。
代替io流的场合我没遇见过……
其实我对很多项目都要自己写一个log模块很不理解……
没往这方面想过……

  回复  更多评论
  

# re: 输入流函数模板决议问题导致程序不简洁 2009-11-21 17:32 唐风
@OwnWaterloo
谢谢推荐,说起来,C++ 的书读了不少,不过 BS 老爷子的《the c++ programming language》还真是没看过……惭愧,回头找来啃一啃。
《标准C++输入输出流与本地化》这本书看了前两章都看不下去了,实在有点无趣,倒是《C++ standard library》中IO部分看几回,但本地化还是没搞明白。

PS:
说到 log 模块,觉得还是蛮有用的,就现在手头上做的项目来说,如果没有这东西,所有模块的 log 信息不受任何控制的话,光是这些 log 的通信就能把机器卡死(因为机器与PC的是串口连的)……
觉得对于大一点的软件(虽然我只接触了一个)来说,系统自身的调试机能和控制都蛮重要。  回复  更多评论
  

# re: 输入流函数模板决议问题导致程序不简洁 2009-11-21 18:04 OwnWaterloo
@唐风
C++老豆的书得看~_~ 确实是对C++理解最深的人。
本地化在《TC++PL》的附录(好像特别版才有附录)中有,不过好像没想象中的那么好用……


关于log…… 特指debug log 我不是说它的效率…… 而是为什么需要这种东西……
我的想法是,能测试就不要用调试器,能用调试器就不要写debug log,只有不得已的时候,才会用那个东西。
为什么不把bug都查出来?
而是将它隐藏到软件中,等它出现后 —— 交付给用户使用后 —— 再从debug log中研究哪出的bug?

当然,我也没什么大型软件的经验,也没有用户逻辑复杂的软件的经验……
可能那时候会理解debug log的用处……

  回复  更多评论
  

# re: 输入流函数模板决议问题导致程序不简洁 2009-11-22 14:40 OwnWaterloo
@唐风
后来又回味了一下,用SFINAE,故意让使用某些类型实例化模板时产生错误,从而从重载候选中剔除,确实是一个很好的办法~_~

boost的enable_if也确实抓住并满足了大量需求。

好东西啊,再回味回味...

  回复  更多评论
  

# re: 输入流函数模板决议问题导致程序不简洁 2009-11-22 15:38 唐风
@OwnWaterloo
是的啊,正好我也在“回味”,哈哈,准备作为那篇 SFINAE 随笔的补充,加一些 enable_if 的笔记……

对于 enable_if 的还有一个比较舒服的功能点是:
boost::disable_if<boost::is_class<T>, DataFromFile&>::type
这个可以放返回值的位置上生效,并且让条件满足时使用DataFromFile&作为返回类型,其它情况下让它 Substitution fail,而不对程序产生“负面影响”。

真是非常体贴啊,因为在这个地方,运算符重载的参数个数是有限制的,不能随意增加,所以在这里不能直接在参数表中加 T::* p = 0 之类的东西来运用 SFINAE。


  回复  更多评论
  


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