南京大学学位论文 LaTeX 模板

由于女朋友要写毕业论文,一些前人写的模板我不熟悉,并且摘要格式好像都不符合南京大学研究生院要求,所以我就在中科院学位论文模板的基础上给她改了一个南大研究生学位论文 LaTeX 模板。主要工作是将封面和摘要格式都改成符合南大研究生院给的样张格式,实话说,花了不少工夫。

既然模板都已经写好,后来我干脆又完善了一下,添加了博士毕业论文需要的国家图书馆论文封面。这样大概可以作为一个完整的,包含硕士和博士毕业论文格式的南大研究生学位论文 LaTeX 模板包,我将这个 LaTeX 模板释出在下面这个地址:
http://share.solrex.org/njuthesis/,或者Google Code页:http://njuthesis.googlecode.com/
下面是一个 flash 的预览:
http://share.solrex.org/njuthesis/template-preview.swf

有需要的同学可以去下载,最起码可以作为一个修改的基础,希望这些工作能够对别人有所助益。我会尽量地维护这个模板,所以如果有哪位校友觉得有不完善的地方,可以和我联系,我会修正相应的缺点。

字符串参数的模板函数推导问题(续)

前面一篇文章我们讨论了字符串作为参数的模板函数推导问题,下面我们看一下使用不同字符串参数类型对模板函数实例化的影响。代码如下,在语句后面的注释为该句的输出。该输出是 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;
}

类成员函数作为 pthread_create 函数参数

花了三个工作日把原来写的一段通信守护进程代码从过程方法改到了 template class,对于 template 的使用和类的派生明白了不少道理。还有个很受启发的一点,就是 C++ 中如何使用类的成员函数作为创建线程的开始函数。
pthread_create 是 POSIX 标准下创建线程的函数,函数原型是:
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void*(*start_routine)(void *), void *arg);

在 C 中,这个函数使用很简单,只要定义一个参数和返回值均为 void * 类型的函数,使用函数名字作为参数即可。就算不完全符合,可以使用 (void *(*)(void *)) 将其强制转换为符合类型检查规格的函数指针。但是,类的非静态成员函数隐含 this 指针作为第一个参数,所以参数完全不可能转化为 void * 类型,而 C++ 的类型检查要比 C 严格许多。由于我原来写的代码是 C 风格的,自然不会出现类型不符的问题,现在将线程开始函数封装到一个模板类中,再创建线程的时候就不能满足需要了。

在试了几种转换无效之后,从网上搜到一种方法:定义线程开始函数为类的静态成员函数(static member function),这样就不隐含 this 指针了,然后将 this 指针从 pthread_create 最后一个参数传给开始函数,在函数中将 void * 类型的 this 指针强制转换为类指针。看来静态成员函数还是有些妙用的。

===================我叫分割线===================

带着我妈和我妹在北京城里转悠了几天,累得不行。主要原因在我,没有考虑到身体因素,连着玩消耗太大,再加上自己也没车没房,倒公交车和住宾馆也要走很远,费时间又费劲。我都快受不了了,别说我妈了。以后再出去旅游,坚决不会再连着转三天以上,要么紧紧张张地玩两天,要么就花时间长点儿,走走歇歇。唉,谁让咱是穷人呢,又有钱又有闲的日子还没过上呢!

今天看了下积压很久的博客订阅,同学里开始写和继续写的人越来越多了。觉得有些文章比较阴沉低迷,因为自己从大三开始心情就老是跌宕起伏,反而在面对这许多次的分别聚首时坦然一些。要不是周熹在散伙饭时候专门招我,也不会哭得那么厉害。散了散了散了吧,没有离别,怎么会有重逢呢?

还是在 Yourui 的博客上看到小恪去新疆的消息,要是当面看到他,肯定会玩笑说发配三千里伊犁充军去了。这在边关待了两年之后,再回来学积分拓扑之类的数学还能看得下去吗?都说是命运无常,旅途坎坷却能看更美风景,只是不知道那关外还是不是大漠孤烟长河落日的大西北?

刚才去吃饭,走在晚间暖暖的懒洋洋的空气中,忽然有点儿秋天的感觉。想起 7 年前爸爸送我到商丘一高上学的情景,日子可真快啊!老了,老了!