Tag Archives: CPP
前面一篇文章我们讨论了字符串作为参数的模板函数推导问题,下面我们看一下使用不同字符串参数类型对模板函数实例化的影响。代码如下,在语句后面的注释为该句的输出。该输出是 g++ 编译后产生的输出,主要是因为输出简洁,而且我们这里只关心模板函数的不同实例,并不关心 const 类型。
#include <iostream>
#include <typeinfo>
#include <vector>
#include <string>
using namespace std;template<typename T>
void foo(const T& t)
{
cout << "foo: generic(" << t << ") " << typeid(t).name() << endl;
}template<typename T>
void bar(const T t)
{
cout << "bar: generic(" << t << ") " << typeid(t).name() << endl;
}/*
$ c++filt [-t] A1_c A2_c A3_c Ss PKc
char [1]
char [2]
char [3]
std::basic_string<char, std::char_traits<char>, std::allocator<char> >
char const*
*/
int main()
{
foo(""); // foo: generic() A1_c
foo("0"); // foo: generic(0) A2_c
foo("01"); // foo: generic(01) A3_c
foo(static_cast<string>("")); // foo: generic() Ss
foo(static_cast<string>("0")); // foo: generic(0) Ss
foo(static_cast<string>("01")); // foo: generic(01) Ss
foo(static_cast<const char *>("")); // foo: generic() PKc
foo(static_cast<const char *>("0")); // foo: generic(0) PKc
foo(static_cast<const char *>("01")); // foo: generic(01) PKc
foo(*(new string(""))); // foo: generic() Ss
foo(*(new string("0"))); // foo: generic(0) Ss
foo(*(new string("01"))); // foo: generic(01) Ss
bar(""); // foo: generic() PKc
bar("0"); // foo: generic(0) PKc
bar("01"); // foo: generic(01) PKc
bar(static_cast<string>("")); // foo: generic() Ss
bar(static_cast<string>("0")); // foo: generic(0) Ss
bar(static_cast<string>("01")); // foo: generic(01) Ss
bar(static_cast<const char *>("")); // foo: generic() PKc
bar(static_cast<const char *>("0")); // foo: generic(0) PKc
bar(static_cast<const char *>("01")); // foo: generic(01) PKc
bar(*(new string(""))); // foo: generic() Ss
bar(*(new string("0"))); // foo: generic(0) Ss
bar(*(new string("01"))); // foo: generic(01) Ss
return 0;
}
基于前一篇博客的分析,我们知道形如 "hello" 的常量字符串在编译时的类型是 char 数组。不同长度的 char 数组,其类型是不一样的,我们可以使用下面语句:
cout << (typeid(char [1]) == typeid(char [2])) << endl;
来验证这一想法。因此,如果我们使用不同长度的字符串作为参数调用 foo,编译器就会为模板函数 foo 实例化不同的实例函数,这一点已经由 foo 的前三个输出验证。我们还可以通过 readelf 来读取目标文件符号表,或者 objdump 查看目标文件反汇编代码中 foo 的实例函数的数量。
$ readelf -s test.o | c++filt -t | less
$ objdump -S test.o | c++filt -t | less
这也就是说,我们使用原始字符串调用了三次 foo,其实是三个不同的实例函数,这样显然会导致目标代码臃肿。那么怎么避免这种情况出现呢?下面我们使用了三种不同的方法,将字符串 static_cast 成 string 或者 const char * 类型,或者使用字符串构造一个 string 对象作为参数,这三种情况都能保证不同(内容)字符串参数的调用使用的是同一个实例化的模板函数。
有没有方法避免类型转换呢?我们可以使用非引用参数类型作为模板函数的模板参数,如 bar 模板函数所示。如前一篇中的分析,此时 char 数组类型会被隐式转换成 char 指针类型,然后进行模板函数推导。所以我们看到即使传的是原始字符串参数,其调用的实例化函数仍然是 char const * 类型的。由于这里类型 T 被推导为 char const * 类型,所以传递的仍然是指针。
但是下面的 string 类型的实例化模板函数实现的就是值传递了,这在函数运行效率上可能会有一些影响。不过现代的函数库对 string 都实现为 copy-on-write(例如 MFC 的 CString 和 Qt 的 QString),我想 STL 的 string 应该也不例外,而 const T 参数并不允许对参数修改,所以效率上的影响应该还是比较小的。只是在语义上与传一个指针就有不同了,假如不限定 T 是 const,那么值传递 string 时,对 string 的修改就无法反映到原来 string 上了。
最后,到底哪个方法好呢?我不知道,我没有足够的实践经验来评论哪种方法更好。我这两篇文章的目的仅仅是探讨一下使用不同形式字符串作为模板函数参数时可能发生的奇怪现象,以及要注意的方面,至于哪种方法更好,可能要留待实际需求来决定。
附:第一段代码的 VS 2008 编译器编译结果执行的输出:
foo: generic() char const [1]
foo: generic(0) char const [2]
foo: generic(01) char const [3]
foo: generic() class std::basic_string,class std::allocator >
foo: generic(0) class std::basic_string,class std::allocator >
foo: generic(01) class std::basic_string,class std::allocator >
foo: generic() char const *
foo: generic(0) char const *
foo: generic(01) char const *
foo: generic() class std::basic_string,class std::allocator >
foo: generic(0) class std::basic_string,class std::allocator >
foo: generic(01) class std::basic_string,class std::allocator >
bar: generic () char const *
bar: generic (0) char const *
bar: generic (01) char const *
bar: generic () class std::basic_string,class std::allocator >
bar: generic (0) class std::basic_string,class std::allocator >
bar: generic (01) class std::basic_string,class std::allocator >
bar: generic () char const *
bar: generic (0) char const *
bar: generic (01) char const *
bar: generic () class std::basic_string,class std::allocator >
bar: generic (0) class std::basic_string,class std::allocator >
bar: generic (01) class std::basic_string,class std::allocator >
国庆长假期间又翻了翻 《C++ Primer》,看到模板函数特化,就想起来以前遇到的一个问题。这个问题我曾经在 TopLanguage 讨论组请教过(链接),今天翻出来又仔细想了想,做一个总结吧。
困惑起源于以字符串作为参数,如何匹配到特化的模板函数。代码如下,其中注释部分是该句对应的输出(使用 VS2008 编译器,一会儿再讨论 g++ 的问题):
#include <iostream>
#include <typeinfo>
using namespace std;template<typename T>
void foo(const T& t)
{
cout << "foo: generic " << typeid(t).name() << endl;
}template<>
void foo<const char *>(const char * const& t)
{
cout << "foo: special " << typeid(t).name() << endl;
}template<typename T>
void bar(const T t)
{
cout << "bar: generic " << typeid(t).name() << endl;
}template<>
void bar<const char *>(const char * t)
{
cout << "bar: special " << typeid(t).name() << endl;
}int main()
{
char str[] = "hello";
const char con_str[] = "hello";
const char * const p = "hello";
foo("hello"); // foo: generic char const [6]
foo(static_cast<const char * const>("hello")); // foo: special char const *
foo(static_cast<const char *>("hello")); // foo: special char const *
foo(str); // foo: generic char const [6]
foo(con_str); // foo: generic char const [6]
foo(p); // foo: special char const *
bar("hello"); // bar: special char const *
bar(str); // bar: generic char *
bar(con_str); // bar: special char const *
bar(p); // bar: special char const *
cout << typeid("hello").name() << endl; // char const [6]
cout << typeid(str).name() << endl; // char [6]
cout << typeid(con_str).name() << endl; // char const [6]
cout << typeid(p).name() << endl; // char const *
return 0;
}
首先让我奇怪的问题是,第一个 foo 函数调用 foo("hello"),为什么实际调用的不是特化的 foo 函数?
其实这个例子是有起源的,《C++ Primer》第四版 Section 16.6.1 的最后给出这样一个例子:
// define the general compare template
template <class T>
int compare(const T& t1, const T& t2) { /* ... */ }int main() {
// uses the generic template definition
int i = compare("hello", "world");
// ...
}// invalid program: explicit specialization after call
template<>
int compare<const char*>(const char* const& s1,
const char* const& s2)
{ /* ... */ }
并解释说:
This program is in error because a call that would match the specialization is made before the specialization is declared. When the compiler sees a call, it must know to expect a specialization for this version. Otherwise, the compiler is allowed to instantiate the function from the template definition.
那么我认为作者暗含的意思里有,compare("hello", "world") 这个函数调用是 match 特化的 compare 函数的。但是从我们给出的第一段代码输出来看,并不是这个样子的,所以我谨慎地怀疑,《C++ Primer》给出的这个例子是有错的。虽然这段程序的确有错,但是即使将特化函数提到前面,compare("hello", "world") 仍然不会调用该特化函数。
请教了别人、书本和标准之后,下面我试着对上面每句的输出做一下解释(当然,可能有错,请指正):
1. foo("hello"); // foo: generic char const [6]
"hello"具有类型 char const [6],由于 foo 模板使用的是引用参数,因此数组实参不会被转换成指针,而是追求一个较为精确的匹配,因此编译器实例化一个 void foo<char const [6]>(const char (& t)[6]) 模板函数(VS2008),这也是为什么我们能看到参数的类型输出是 char const [6];
2. foo(static_cast<const char * const>("hello")); // foo: special char const *
"hello"被 cast 成了 const char * const 类型,自然与特化的函数 void foo<const char *>(const char * const& t) 能够精确匹配,因此调用的是特化的 foo;
3. foo(static_cast<const char *>("hello")); // foo: special char const *
"hello"被 cast 成了 const char * 类型,虽然少了一个 const,但是 C++ 标准中有这样的说法:
14.8.2.3
If the orignial A is a reference type, A can be more cv-qualified than the deduced A
这种 cv-qualifier 并不影响推导,最终仍然是匹配到特化的 foo 函数;
4. foo(str); // foo: generic char const [6]
str 和 "hello" 也是仅仅相差一个 cv-qualifier,也不影响推导,其结果与 1 是一致的;
5. foo(con_str); // foo: generic char const [6]
con_str 和 "hello" 的类型一样,显然其结果与 1 应是一致的;
6. foo(p); // foo: special char const *
p 的类型其实就是 2 中参数被 cast 之后的类型,显然其结果应该与 2 一致;
7. bar("hello"); // bar: special char const *
乍一看就有些奇怪,为什么把模板参数换成值(而不是引用),特化的情况就与 foo 不同了呢?C++ 标准中有这样的规定:
14.8.2.3
If A is not a reference type:
-- If P is an array type, the pointer type produced by the array-to-pointer standard conversion (4.2) is used in place of P for type deduction;
因此,这里 "hello" 原本是一个数组类型,由于模板的参数不是引用类型,所以 "hello" 的类型被转换为指针类型 char const * 参加推导,正好与特化的 bar 函数匹配;
8. bar(str); // bar: generic char *
由于模板参数不是引用类型,没有 const 限定的 str 无法匹配特化的 bar,因此编译器实例化一个 void bar<char *>(char * t) 模板函数;
9. bar(con_str); // bar: special char const *
由于 con_str 与 "hello" 的类型一样,因此其结果与 7 是一致的;
10. bar(p); // bar: special char const *
这里 p 的类型本身就是特化函数的参数类型,显然要被推导为调用特化函数。
解释完了字符串参数的模板函数推导问题,下面来讨论一下 g++ 和 VS2008 的不同。上面同样的代码,使用 g++ 编译之后,输出是这个样子的:
foo: generic A6_c
foo: special PKc
foo: special PKc
foo: generic A6_c
foo: generic A6_c
foo: special PKc
bar: special PKc
bar: generic Pc
bar: special PKc
bar: special PKc
A6_c
A6_c
A6_c
PKc
当然,需要解释的是 g++ 内部对符号的字面做了一些变化,我们可以使用 c++filt demangle 这些符号:
$ c++filt [-t] A6_c PKc Pc
char [6]
char const *
char *
与 VS2008 的输出相比,我有一个疑问,为什么 g++ 没有为 const char [6] 输出正确的 const 类型名呢?
还有,我们提到了第 1 种情况下,编译器为 foo("hello") 调用实例化了一个 void foo<char const [6]>(const char (& t)[6]) 类型的函数。假如我们提供了一个类似的特化函数,那么 foo("hello") 会调用该特化函数;但是,使用 g++ 编译器时,特化函数的类型必须是 void foo<char [6]>(const char (& t)[6]) 而不是 void foo<char const [6]>(const char (& t)[6]),这让我感觉非常奇怪。只有不提供模板参数时,比如 void foo(const char (& t)[6]),两个编译器才能都推导出调用特化函数。
需要验证的话,您可以尝试在第一段代码中增加下面两个特化函数,再在两个编译器上编译那段代码:
template<>
void foo<char [6]>(const char (& t)[6])
{
cout << "foo: special<char [6]> " << typeid(t).name() << endl;
}template<>
void foo<char const [6]>(const char (& t)[6])
{
cout << "foo: special<char const [6]> " << typeid(t).name() << endl;
}
This patch enables qRFCview to load proxy settings from environment variables such as "http_proxy" and "socks_proxy". Yes, popping up a dialog to set these things is fancier, but that means more coding work. I am pretty satisfied with this solution.
You can download the modified source code tarball from http://share.solrex.org/ibuild/qrfcview-0.62-solrex2.tar.gz . Binary packets for special Linux distributions can be found at http://share.solrex.org/ibuild/ too.
diff -aurN qrfcview-0.62/src/mainwindow.cpp qrfcview-0.62-solrex/src/mainwindow.cpp
--- qrfcview-0.62/src/mainwindow.cpp 2006-01-13 17:56:45.000000000 +0800
+++ qrfcview-0.62-solrex/src/mainwindow.cpp 2009-07-14 16:03:56.000000000 +0800
@@ -122,8 +122,10 @@
{
// Load a RFC number
bool bOK;
+ /* NOTE 20090714/Solrex <http://solrex.org>:
+ Enlarge RFC number limit to 10000. */
int iRFCNum = QInputDialog::getInteger(this, tr("Please enter a RFC number"),
- tr("RFC#:"), 0, 1, 5000, 1, &bOK);
+ tr("RFC#:"), 0, 1, 10000, 1, &bOK);
if (bOK)
RFCLoad( iRFCNum );
}
diff -aurN qrfcview-0.62/src/rfcloader.cpp qrfcview-0.62-solrex/src/rfcloader.cpp
--- qrfcview-0.62/src/rfcloader.cpp 2006-01-13 17:56:45.000000000 +0800
+++ qrfcview-0.62-solrex/src/rfcloader.cpp 2009-07-14 15:58:50.000000000 +0800
@@ -25,11 +25,41 @@
#include <QMessageBox>
#include <QtDebug>
#include <QDir>
+#include <QNetworkProxy>QRFCLoader::QRFCLoader(QObject *parent)
: QObject(parent)
{
m_qHttp=new QHttp(this);
+ /* NOTE 20090714/Solrex <http://solrex.org>:
+ Detect proxy settings via system environment variable
+ ``http_proxy'' and ``socks_proxy''. */
+ char *p;
+ QNetworkProxy qNetworkProxy(QNetworkProxy::NoProxy);
+ if ((p = getenv("socks_proxy")) != NULL) {
+ qNetworkProxy.setType(QNetworkProxy::Socks5Proxy);
+ } else if ((p = getenv("http_proxy")) != NULL) {
+ qNetworkProxy.setType(QNetworkProxy::HttpProxy);
+ }
+ if (p != NULL) {
+ QString proxyStr = p;
+ proxyStr = proxyStr.trimmed();
+ proxyStr.remove("http://");
+ QStringList list = proxyStr.split("@");
+ QStringList list1 = list[0].split(":");
+ if (list.count() > 2) {
+ qNetworkProxy.setType(QNetworkProxy::NoProxy);
+ qDebug() << "Unresolvable proxy setting:" << list;
+ } else if ( list.count() == 2) {
+ qNetworkProxy.setUser(list1[0]);
+ qNetworkProxy.setPassword(list1[1]);
+ list1 = list[1].split(":");
+ }
+ qNetworkProxy.setHostName(list1[0]);
+ qNetworkProxy.setPort(list1[1].toInt());
+ }
+ m_qHttp->setProxy(qNetworkProxy);
+ qDebug() << "Loaded proxy:" << p;
connect(m_qHttp, SIGNAL( requestStarted(int) ), this, SLOT( startDownload(int) ) );
connect(m_qHttp, SIGNAL( requestFinished(int, bool) ), this, SLOT( fileDownload(int, bool) ) );
connect(m_qHttp, SIGNAL( responseHeaderReceived(QHttpResponseHeader) ), this, SLOT( receivedHeader(QHttpResponseHeader) ) );
RFC number limit issue was discussed at deb-packages-of-qrfcview-and-jabref.html .
这是一篇很古老的文章,翻译得不好,大家将就着看。 固定链接:http://share.solrex.org/os/ee_vs_cs_cn.html 从前,在一个离这儿不远的国家,一个国王召来他的两个谋臣进行一项测试。国王给他们看了一个闪闪发光的金属盒,盒子上面有两个插槽,一个控制按钮和一个手柄,然后问道:“你们认为这是个什么东西?” 其中一个谋臣,电子工程师,抢先答道:“陛下,这是一个烤吐司机。”国王问他:“如果让你给它设计一个嵌入式计算机,你会怎么做?”这个工程师回答说:“利用一个4位的微控制器即可,我将写一个简单的程序读入亮度盘(译注:the darkness knob,不知道是什么东西),将其量化到从雪白到煤黑的 16 阶亮度水平上。这个程序将使用该亮度水平作为 … Continue reading
晚上看了两页 The Art of Unix Programming,其中提到了一个我以前一直感觉困惑的地方: 在我看过的 C/C++ … Continue reading
不知不觉间立冬已经过了。走在校园里一派萧瑟的感觉,这几天连着下了几场雨,空气湿漉漉的,让人顿生悲秋之感。 浦园的四季还算漂亮,在秋天也是如此,今天从机房交完作业出来的路上,看到象塔一样银杏树叶子一片金黄,够得上火树银花了。可惜没有相机,真应该照下来的。杨树的叶子已经落得差不多了,梧桐还有些黄黄的意思。也过不了几天,可能就只看得见草地和松树是绿的了(南大的草坪下面是有加热管道的,草的待遇都比人高)。 去年,不记得什么时候了,去鼓楼时候正好碰上打银杏果,下面好大一块塑料布接着,那面的树可是比这大多了。 考完试了,忽然有一种重任离去的感觉,不自觉的就觉得没有目标了,有种茫然的态度。实际静下心来想一想有好多事情要做的,不说别的,光学习已经够让人心烦的了。 昨天(准确的说应该是前天,已经凌晨了),考完后就在宿舍里打了几局游戏,不过也没有光玩,还是把自己的网站更新了一些东西。今天起来把数值计算的实验程序调了一下,是求特征值的。因为最后结果跟初始向量的选取有关,可是怎么选还是只能出来四个,最小的那个是千呼万唤也不出来,没办法只好就这样交了,本来老师也只让求两个就行。我觉得这应该也是一个课题吧,初始向量怎么选取能取到所有的特征值,有空的时候好好想一想。下午本学期第二次去上那个欧洲电影专题的选修课,看的是“最后的地铁”,一部法国电影,讲的是二战时候一个法国剧院在纳粹统治下的抗争,一个女人和两个男人的故事,没看明白,好多隐喻由于没有背景知识不能很了解。晚上自习到十点半,没看多少书。 过两天就是自己生日了,十八岁生日没有好好过,就那样过去了。今年农历和阳历生日凑巧是一天,打算和宿舍的人出去撮一顿,也好久没有出去吃饭了。 现在很少静下心来想东西了,出去自习发呆吧也是常有的事情,不过总是逼着自己回过神。回到寝室又乱糟糟的,我们屋属于那种人流量特大那种,唉,热闹是热闹,有时候也烦的要命。你写个或者做个什么东西一会儿就有一人探头过来:“干吗呢?”不安生。我也就半夜或者没人的白天写写了。还记得大一的时候经常早上不起在床上想人生的意义,追求了什么的东西。这会儿变得很世俗,想也没用,该咋地还是咋地。本来未来就有很多不确定性,有时候是会有点茫然。 买的一本《英语学习》的合订本上有篇文章挺好的,讲的是一个人应该怎样学会放下那些对自己不重要的东西,每个人都应该有个计划,计划要做的和不要做的事情。在十九岁生日到来之际,我觉得我有没有必要也列一个计划呢,最近是有点想法。 … Continue reading
