STL笔记(5)条款49:学习破解有关STL的编译器诊断信息

条款49:学习破解有关STL的编译器诊断信息

用一个特定的大小定义一个vector是完全合法的,

vector<int> v(10);    // 建立一个大小为10的vector

而string在很多方面像vector,所以你可能希望可以这么做:

string s(10);        // 常识建立一个大小为10的string

这不能编译。string没有带有一个int实参的构造函数。我的一个STL平台像这样告诉我那一点:

example.cpp(20): error C2664:'__thiscall std::basic_string<char, struct
std::char_traits<char>,class std::allocator<char> >::std::basic_string<char,
struct std::char_traits<char>,class std::allocator<char> >(const class
std::allocator<char> &)': cannot convert parameter 1 from 'const int' to 'const
class std::allocator<char> &' Reason: cannot convert from 'const int' to 'const
class std::allocator<char>
No constructor could take the source type, or constructor overload resolution was ambiguous

是不是很奇妙?消息的第一部分看起来像一只猫走过键盘,第二部分神秘地提到了一个从未在源代码中涉及的分配器,第三部分说构造函数调用是错的。当然,第三部分是准确的,但首先让我们关注于号称猫咪散步的结果上,因为当使用string时,这是你经常遇到的诊断信息的典型。

string不是一个类,它是typedef。实际上,它是这个的typedef:

basic_string<char, char_traits<char>, allocator<char> >

这是因为字符串的C++观念已经被泛化为表示带有任意字符特性(“traits”)的任意字符类型的序列并储存在以任意分配器分类的内存中。在C++里所有类似字符串的对象实际上都是basic_string模板的实例,这就是为什么当大多数编译器发出关于“程序错误使用string”的诊断信息时会涉及类型basic_string。(一些编译器很善良,在诊断信息中会使用string的名字,但大多数不会。)通常,那样的诊断信息会明确指出basic_string(以及服务助手模板char_traits和allocator)在std名字空间里,所以常常看到错误调用string会产生提及这种类型的诊断信息:

std::basic_string<char, std::char_traits<char>, std::allocator<char> >

这十分接近于上面编译器里使用的诊断信息,但不同的编译器使用这个主题的不同变体。我使用的另一个STL平台以这种方式表示string,

basic_string<char, string_char_traits<char>, __default_alloc_template<false,0> >

string_char_traits和__default_alloc_template的名字是非标准的,但是那是生活。一些STL实现背离了标准。如果你不喜欢你当前STL实现里的背离,考虑用另一个来替换它。条款50给了你可以找到可选择实现的位置的例子。

不管编译器诊断信息怎样表示string类型,把诊断信息减少到有意义的东西的技术是一样的:用文字“string”全局替换冗繁难解的basic_string。如果你使用的是命令行编译器,通常可以很容易地用一个类似sed的程序或一种脚本语言比如perl、python或ruby来完成。(你可以在Zolman的文章——《Visual C++的STL错误信息解码器》[26]——里找到一个这样的脚本的例子。)就上面的诊断信息而言,我们用string全局替换

std::basic_string<char, struct std::char_traits<char>,
class std::allocator<char> >

可以得到这个:

example.cpp(20): error C2664:'__thiscall string::string(const class
std::allocator<char> &)': cannot convert parameter 1 from 'const int' to const
class std::allocator<char> &'

这会清楚(或至少比较清楚)地说明问题是在传给string构造函数的参数类型里,即使仍然神秘地提及allocator<char>,但比较容易使人发现不存在只带有大小的string构造函数形式。

顺便说一下,神秘地提到分配器的原因是每个标准容器都有一个只带有分配器的构造函数。就string而论,是三个可以用一个实参调用的构造函数之一,但由于某种原因,编译器指出带有分配器的那个是你试图调用的。编译器指错了,而诊断信息也令人误解。哦哟。

至于只带有分配器的构造函数,请不要使用它。那个构造函数是为了容易构造类型相同但分配器不等价的容器。通常,那是不好的,非常不好。要知道为什么,转向条款11

现在让我们对付更富于挑战性的诊断信息。假定你正在实现一个允许用户通过昵称而不是电子邮件地址查找人的电子邮件程序。例如,这样的程序将可能使用“The Big Cheese”作为美国总统(碰巧是president@whitehouse.gov)电子邮件地址的同义词。这样的程序可以使用一个从昵称到电子邮件地址的映射,并可能提供一个成员函数showEmailAddress,显示和给定的昵称关联的电子邮件地址:

class NiftyEmailProgram {
private:
    typedef map<string, string> NicknameMap;
    NicknameMap nicknames;                // 从昵称到电子邮件
                            // 地址的映射
public:
    
    void showEmailAddress(const string& nickname) const;
};

在showEmailAddress内部,你需要找到与一个特定的昵称关联的映射入口,所以你可能这么写:

void NiftyEmailProgram::showEmailAddress(const string& nickname) const
{
    
    NicknameMap::iterator i = nicknames.find(nickname);
    if (i != nicknames. end()) 
    
}

编译器不喜欢这个,而且有好的原因,但原因不明显。为了帮助你指出它,这是一个STL平台有帮助地发出的:

example.cpp(17): error C2440: 'initializing': cannot convert from 'class
std::_Tree<class std::basic_string<char, struct std::char_traits<char>,class
std::allocator<char> >,struct std::pair<class std::basic_string<char, struct
std::char_traits<char>,class std::allocator<char> > const .class
std::basic_string<char, struct std::char_traits<char>,class std::allocator<char> >
>,struct std::map<class std::basic_string<char, struct
std::char_traits<char>,class std::allocator<char> >.class std::basic_string<char,
struct std::char_traits<char>,class std::allocator<char> >,struct std::less<class
std::basic_string<char,structstd::char_traits<char>, class
std::allocator<char> > >,class std::allocator<class std::basic_string<char, struct,
std::char_traits<char>,class std::allocator<char> > > >::_Kfn, struct
std::less<class std::basic_string<char, struct std::char_traits<char>,class
std::allocator<char> > >,class std::allocator<class std::basic_string<char, struct,
std::char_traits<char>,class std::allocator<char> > > >::const_iterator' to 'class
std::_Tree<class std::basic_string<char, struct std::char_traits<char>,class
std::allocator<char> >,struct std::pair<class std::basic_string<char, struct
std::char_traits<char>,class std::allocator<char> > const .class
std::basic_string<char, struct std::char_traits<char>,class std::allocator<char> >
>,struct std::map<class std::basic_string<char, struct
std::char_traits<char>,class std::allocator<char> >,class std::basic_string<char,
struct std::char_traits<char>,class std::allocator<char> >,struct std::less<class
std::basic_string<char,structstd::char_traits<char> .class
std::allocator<char> > >,class std::allocator<class std::basic_string<char,struct
std::char_traits<char>,class std::allocator<char> > > >::_Kfn, struct
std::less<class std::basic_string<char, struct std::char_traits<char>,class
std::allocator<char> > >,class std::allocator<class std::basic_string<char, struct
std::char_traits<char>,class std::allocator<char> > > >::iterator'
No constructor could take the source type, or constructor overload resolution was ambiguous

有2095个字符长,这条消息看起来相当可怕,但我看过更糟的。对于这个例子我最喜欢的STL平台之一产生了一个4812个字节的诊断信息。正如你所猜测的,错误信息以外的特性是造成我喜爱它的原因。

让我们把这团乱麻减少成容易处理的东西。我们从把basic_string乱语替换成string开始。可以产生这个:

example.cpp(17): error C2440: 'initializing': cannot convert from 'class std::_Tree<class string, struct std::pair<class string const,class string >,struct
std::map<class string, class string, struct std::less<class string >,class
std::allocator<class string > >::_Kfn, struct std::less<class string >,class
std::allocator<class string > >::const_iterator' to 'class std::_Tree<class string,
struct std::pair<class string const .class string >,struct std::map<class string,
class string, struct std::less<class string >,class std::allocator<class string>
>::_Kfn,struct std::less<class string >,class std::allocator<class string>
>::iterator'
No constructor could take the source type, or constructor overload resolution was ambiguous

好多了。现在瘦身到745个字符了,我们可以真正地开始看消息了。很可能引起我们注意的东西之一是模板std::_Tree。标准没有说过一个叫_Tree的模板,但名字中的前导下划线随后有一个大写字母唤起了我们的记忆——这样的名字是为实现而保留。这是用来实现STL一些部分的一个内部模板。

实际上,几乎所有STL实现都使用某种内在的模板来实现标准关联容器(set、multiset、map和multimap)。就像使用string的源代码通常导致诊断信息提及basic_string一样,使用标准关联容器的源代码经常会导致诊断信息提及一些内在的树模板。在这里,它叫做_Tree,但我知道的其他实现使用__tree或__rb_tree,后者反映出使用红-黑树——在STL实现中最常使用的平衡树类型。(译注:红黑树的知识可以在数据结构或算法的相关书籍里找到。)

把_Tree先放到一边,上面的消息提到了一个我们得认出的类型:std::map<class string, class string, struct std::less<class string>, class std::allocator<class string > >。这正好是我们正使用的map类型,除了显示了比较和分配器类型(我们定义map时没有指定它们)。如果我们用那个类型的typedef——NicknameMap——替换了它,错误信息将更容易明白。于是产生了这个:

example.cpp(17): error C2440: 'initializing': cannot convert from 'class
std::_Tree<class string, struct std::pair<class string const, class string >,struct
NicknameMap::_Kfn, struct std::less<class string >,class std::allocator<class
string > >::const_iterator' to 'class std::_Tree<class string, struct std::pair<class
string const ,class string >,struct NicknameMap::_Kfn, struct std::less<class
string >,class std::allocator<class string > >::iterator'
No constructor could take the source type, or constructor overload resolution was ambiguous

这条信息更短,但清楚得多。我们需要对_Tree做一些事情。因为_Tree是一个实现特定(implementation-specific)的模板,知道它的模板参数的意思的唯一的方法是去读源代码,而如果不必,没有理由要去翻寻实现特定的源代码。让我们试着只是用SOMETHING替换传给的_Tree的全部东西来看看我们得到什么。这是结果:

example.cpp(17): error C2440: 'initializing': cannot convert from 'class
std::_Tree<SOMETHING>::const_iterator to 'class
std::_Tree<SOMETHING>::iterator'
No constructor could take the source type, or constructor overload resolution was ambiguous

是我们能够处理的东西。编译器抱怨我们试图把某种const_iterator转换成iterator,一次对常数正确性的明显破坏。让我们再次看看那段讨厌的代码,我已经高亮了引起编译器怒火的那行:

class NiftyEmailProgram {
private:
    typedef map<string, string> NicknameMap;
    NicknameMap nicknames;

public:
    
    void showEmailAddress(const string& nickname) const;
};

void NiftyEmailProgram::showEmailAddress(const string& nickname) const
{
    
    NicknameMap::iterator i = nicknames.find(nickname);
    if (i != nicknames.end())
    
}

有意义的唯一解释是我们试图用一个从map::find返回的const_iterator初始化i(是iterator)。那好像很古怪,因为我们是在nicknames上调用find,而nicknames在非常量对象,find因此应该返回非常量iterator。

再看看。是的,nicknames被声明为一个非常量map,但showEmailAddress是一个const成员函数,而在一个const成员函数内部,类的所有非静态数据成员都变成常量!在showEmailAddress内部,nicknames是一个常量map。突然错误信息有意义了。我们试图产生一个进入我们许诺不要修改的map中的iterator。要解决这个问题,我们必须把i改为const_iterator或我们必须使showEmailAddress成为一个非const成员函数。这两个解决方案的挑战性或许比发现错误信息的意思更少。

在本条款中,我演示了用原文替换降低错误信息的复杂度,但一旦你稍微实践,多数时间里你将可以在头脑中进行替换。我不是音乐家(我连开收音机都有困难),但别人告诉我好的音乐家可以在一瞥之间视读几个小节;他们不需要看独立的音符。有经验的STL程序员发展出一项类似的技能。他们可以不假思索地在内部把比如std::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >翻译为string。你也要发展这项技能,但在你能做到之前,记得你总是可以通过用更短的记忆术替换冗长的基于模板的类型名字来把编译器诊断信息降低到可以理解的东西。在许多场合,你要做的就是用你已经使用的typedef名字替换typedef展开。那是我们用NicknameMap替换std::map<class string, class string, struct std::less<class string >, class::allocator<class string > >时所做的。

这里有一些应该能帮助你理解有关STL的编译器消息的其它提示:

  • 对于vector和string,迭代器有时是指针,所以如果你用迭代器犯了错误,编译器诊断信息可能会提及涉及指针类型。例如,如果你的源代码涉及vector<double>::iterator,编译器消息有时会提及double*指针。(一个值得注意的例外是当你使用来自STLport的STL实现,而且你运行在调试模式。那样的话,vector和string的迭代器干脆不是指针。对STLport和它调试模式的更多信息,转向条款50。)
  • 提到back_insert_iterator、front_insert_iterator或insert_iterator的消息经常意味着你错误调用了back_inserter、front_inserter或inserter,一一对应,(back_inserter返回back_insert_iterator类型的对象,front_inserter返回front_insert_iterator类型的对象,而inserter返回insert_iterator类型的对象。关于使用这些inserter的信息,参考条款30。)如果你没有调用这些函数,你(直接或间接)调用的一些函数做了。
  • 类似地,如果你得到的一条消息提及binder1st或binder2nd,你或许错误地使用了bind1st或bind2nd。(bind1st返回binder1st类型的对象,而bind2nd返回binder2nd类型的对象。)
  • 输出迭代器(例如ostream_iterator、ostreambuf_iterators(参见条款29),和从back_inserter、front_inserter和inserter返回的迭代器)在赋值操作符内部做输出或插入工作,所以如果你错误使用了这些迭代器类型之一,你很可能得到一条消息,抱怨在你从未听说过的一个赋值操作符里的某个东西。为了明白我的意思,试着编译这段代码:
    vector<string*> v;                    // 试图打印一个
    copy(v.begin(), v.end(),                // string*指针的容器,
        ostream_iterator<string>(cout, "\n"));    // 被当作string对象
  • 你得到一条源于STL算法实现内部的错误信息(即,源代码引发的错误在<algorithm>中),也许是你试图给那算法用的类型出错了。例如,你可能传了错误种类的迭代器。要看看这样的用法错误是怎样报告的,通过把这段代码喂给你的编译器来启发(并愉快!)自己:
    list<int>::iterator i1, i2;        // 把双向迭代器
    sort(i1, i2);            // 传给一个需要
                        // 随机访问迭代器的算法
  • 你使用常见的STL组件比如vector、string或for_each算法,而编译器说不知道你在说什么,你也许没有#include一个需要的头文件。正如条款48的解释,这问题会降临在长期以来都可以顺利编译而刚移植到新平台的代码。

STL笔记(5)条款49:学习破解有关STL的编译器诊断信息的更多相关文章

  1. EC读书笔记系列之19:条款49、50、51、52

    条款49 了解new-handler的行为 记住: ★set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用 ★Nothrow new是一个颇为局限的工具,∵其只适用于内存 ...

  2. STL笔记(4)关于erase,remove

    STL笔记(4)关于erase,remove 你要erase的元素很容易识别.它们是从区间的“新逻辑终点”开始持续到区间真的终点的原来区间的元素.要除去那些元素,你要做的所有事情就是用那两个迭代器调用 ...

  3. 跟着鸟哥学Linux系列笔记3-第11章BASH学习

    跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 跟着鸟哥学Linux系列笔记1 跟着鸟哥学Linux系列笔记2-第10章VIM学习 认识与学习bash 1. ...

  4. MXNet设计笔记之:深度学习的编程模式比较

    市面上流行着各式各样的深度学习库,它们风格各异.那么这些函数库的风格在系统优化和用户体验方面又有哪些优势和缺陷呢?本文旨在于比较它们在编程模式方面的差异,讨论这些模式的基本优劣势,以及我们从中可以学到 ...

  5. [原创]java WEB学习笔记75:Struts2 学习之路-- 总结 和 目录

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  6. [原创]java WEB学习笔记66:Struts2 学习之路--Struts的CRUD操作( 查看 / 删除/ 添加) 使用 paramsPrepareParamsStack 重构代码 ,PrepareInterceptor拦截器,paramsPrepareParamsStack 拦截器栈

    本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...

  7. STL笔记(3) copy()之绝版应用

    STL笔记(3) copy()之绝版应用 我选用了一个稍稍复杂一点的例子,它的大致功能是:从标准输入设备(一般是键盘)读入一些整型数据,然后对它们进行排序,最终将结果输出到标准输出设备(一般是显示器屏 ...

  8. STL笔记(1)map

    STL笔记(1)map STL之map ZZ from http://hi.baidu.com/liyanyang/blog/item/d5c87e1eb3ba06f41bd576cf.html 1. ...

  9. STL笔记(6)标准库:标准库中的排序算法

    STL笔记(6)标准库:标准库中的排序算法 标准库:标准库中的排序算法The Standard Librarian: Sorting in the Standard Library Matthew A ...

随机推荐

  1. JS数组2(冒泡排列、数组里面查找数据)

    数组 一.冒泡排列 对数组attr = [1,8,6,4,5,3,7,2,9]进行由大到小排列,用冒泡排列的方法排列时,会对数组进行比较互换.如果前一个数字较大,这2个元素排列方式不变,如果后一个元素 ...

  2. Altium Designer 使用小结

    今天刚把做好的PCB文件交给工厂去制板,阶段工作告一段落,来一个小总结. 前一段时间复习完C语言之后,在中国知网上搜索用单片机实现的小制作,找比较有意思,又不需要太多外专业知识的东西,然后就相中了超声 ...

  3. sourcemap的使用

    minify.bat @echo off if ""%1""=="""" goto end :loop if not e ...

  4. php中用于接受表单数据的$_request与$_post、$_get

    一.$_request与$_post.$_get的区别和特点 $_REQUEST[]具用$_POST[] $_GET[]的功能,但是$_REQUEST[]比较慢.通过post和get方法提交的所有数据 ...

  5. Sqli-labs less 24

    Less-24 Ps:本关可能会有朋友和我遇到一样的问题,登录成功以后没有修改密码的相关操作.此时造成问题的主要原因是logged-in.php文件不正确.可重新下载解压,解压过程中要主要要覆盖. 本 ...

  6. Cortex-M3/4的Hard Fault调试方法

    1 Cortex-M3/4的Fault简介 Cortex-M3/4的Fault异常是由于非法的存储器访问(比如访问0地址.写只读存储位置等)和非法的程序行为(比如除以0等)等造成的.常见的4种异常及产 ...

  7. MEAN实践——LAMP的新时代替代方案(上)

    摘要:90 年代,LAMP 曾风靡一时,然而随着需求的变迁和数据流量的激增,LAMP 已不可避免的走下神坛.近日,在 MongoDB Blog 中,Dana Groce 介绍了一个基于新时代架构的实践 ...

  8. 图解 javascript 作用域链

    还是之前那一段简单的javascript代码: window.onload=function(){ function sub(a,b){ return a-b; } var result=sub(10 ...

  9. POJ 1953

    //FINBONACI数列 #include <iostream> #define MAXN 100 using namespace std; int _m[MAXN]; int main ...

  10. POJ 2533 Longest Ordered Subsequence

    题目描述:LIS(Longest Increasing Subsequence)模板题 分析:O(n^2)的方法 状态表示:d[i]表示以i结尾的最长上升子序列长度 转移方程:d[i]=max{ 1, ...