coolEx

Today will be better

java遇到的问题记录

问题:使用mvn jetty:run启动遇到:java.lang.LinkageError: javax/management/MBeanServer

解决:启动的时候加上参数:-Dorg.mortbay.jetty.webapp.parentLoaderPriority=true

 

问题:jdbc连接mysql数据库,timestamp类型字段没有插入值,导致select的时候提示:java.sql.SQLException:   Value   ’0000-00-00 ‘   can   not   be   represented   as   java.sql.Timestamp
解决:在jdbc连接的url上加上参数zeroDateTimeBehavior=convertToNull

 

问题:强制指定jvm启动的时候的时区
解决:在MAVEN_OPTS变量中加上-Duser.timezone=Aisa/Shanghai 强制指定为北京时间

nokia n9刷机

昨天晚上更新developer-mode的时候,更新完成之后n9就再也没有起来过,屏幕上一直显示nokia,然后就没有然后了。
记录下早上刷机的过程。
首先安装flasher,下载地址在,需要下载里面的3.12.1版本。
然后下载固件,找了个国行的pr1.2固件:大概有1.2G,还好公司的网速比较快,2min搞定。
在下载的间隙,为flasher写了个ebuild文件,方便卸载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
EAPI=4

MY_A="${PN}_3.12.1_amd64.deb"

DESCRIPTION="Maemo Flasher-3.12.1 Tool for Harmattan, installation package for Debian based Linuxes (x86, 64-bit)"
HOMEPAGE="http://tablets-dev.nokia.com/maemo-dev-env-downloads.php"
SRC_URI="${MY_A}"

LICENSE="Nokia-EULA"
SLOT="0"
KEYWORDS="~amd64"
IUSE=""
RESTRICT="fetch strip"

DEPEND="virtual/libc
        app-arch/deb2targz"
RDEPEND="${DEPEND}"

pkg_nofetch() {
    elog "Please obtain ${P} from
        http://tablets-dev.nokia.com/maemo-dev-env-downloads.php
        and place it in ${DISTDIR}"
}

src_unpack() {
    unpack ${A}
    mkdir -p ${S}
    tar xf data.tar.gz -C ${S}
}

src_install() {
    cp -R ${S}/* ${D}/ || die "Install failed!"
}

调试了下ebuild,这个时候固件早已经下载好了,然后就是刷机了。
在命令行执行(需要在root权限下执行,否则会提示:Error claiming USB interface: Operation not permitted)

1
flasher -F DFL61_HARMATTAN_30.2012.07-1_PR_LEGACY_003-OEM1-958_ARM.bin -f

-F后面的参数是刚才下载的固件名字。回车之后,控制台会提示usb接口没有找到设备:

1
2
3
4
5
flasher 3.12.1 (Oct  5 2011) Harmattan
WARNING: This tool is intended for professional use only. Using it may result
in permanently damaging your device or losing the warranty.

Suitable USB interface (bootloader/phonet) not found, waiting...

这个时候手机插上usb线,然后按住电源键,直到屏幕上出现一个usb图标,flasher应该就可以识别到手机了。一直等到提示成功之后就OK了。
手机之后会自动重启,第一次启动后,和刚买来一样,会有教程什么的,不过开机后遇到个问题,信息、通讯录等图标有重复。这个时候再到设置里面去恢复设置(这样数据不会丢失)问题就解决了。
这样整个过程貌似只会对/分区有影响,/home/user中的数据分区没有影响,通讯录、短信、照片等都还在,就是之前安装的应用都没有了,又要重新安装。

折腾kde-telepathy

今天折腾kde-telepathy遇到了好几个问题,这里记录下。

1、telepathy-qt安装问题,导致net-im/ktp-common-internals-9999编译失败

net-im/ktp-common-internals-9999在编译的时候提示找不到DBusError头文件,这个文件应该由telepathy-qt提供。用equery查看了下telepathy-qt安装的文件,的确没有发现这个目录。因为本机装的telepathy-qt也是9999版本,去本地git源里面clone了一份,发现里面是有这个文件的,仔细查看了TelepathyQt/CMakeLists.txt文件,发现如果要安装DBusError这个头文件,必须在cmake配置的时候开启ENABLE_EXPERIMENTAL_SERVICE_SUPPORT。搜索这个变量,可以发现最外层的CMakeLists.txt文件定义了:

1
option(ENABLE_EXPERIMENTAL_SERVICE_SUPPORT "Enable compilation of experimental and unstable service side bindings for Telepathy-Qt" FALSE)

然后就是去改ebuild文件了。修改很简单,就是在configure的时候增加这个开关,这里的方式是增加了一个USE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--- /var/lib/layman/kde/net-libs/telepathy-qt/telepathy-qt-9999.ebuild  2012-01-25 12:04:05.791432392 +0800
+++ /usr/local/portage/net-libs/telepathy-qt/telepathy-qt-9999.ebuild   2012-04-06 17:16:13.313718014 +0800
@@ -15,7 +15,7 @@
 LICENSE="LGPL-2.1"
 SLOT="0"
 KEYWORDS=""
-IUSE="debug farsight glib"
+IUSE="debug farsight glib experimental_service"
 
 RDEPEND="
        dev-python/dbus-python
@@ -54,6 +54,7 @@
                $(cmake-utils_use_enable debug DEBUG_OUTPUT)
                $(cmake-utils_use_with glib)
                $(cmake-utils_use_with farsight)
+               $(cmake-utils_use_enable experimental_service EXPERIMENTAL_SERVICE_SUPPORT)
        )
        cmake-utils_src_configure
 }

2、ktp-text-ui编译失败

ktp-text-ui编译的时候提示:

1
2
3
4
usr/include/boost-1_48/boost/type_traits/detail/has_binary_operator.hp:50: Parse error at "BOOST_JOIN"
automoc4: process for /chroot/local/portage/net-im/ktp-text-ui-9999/work/ktp-text-ui-9999_build/logviewer/moc_entity-model.cpp failed: Unknown error
pid to wait for: 0
returning failed..

的问题,发现这个是qt的moc的问题,已经有相关的bug了:
https://bugs.kde.org/show_bug.cgi?id=296137
https://bugreports.qt-project.org/browse/QTBUG-22829
解决方案参照http://cgit.freedesktop.org/gstreamer/qt-gstreamer/commit/?id=2cc5399d70d4d6caf8d05df9ca51f5448d545b67这里的,给所有提示moc失败的类的头文件上加上Boost相关宏定义BOOST_TT_HAS_OPERATOR_HPP_INCLUDED
具体补丁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
diff --git a/logviewer/conversation-date-picker.h b/logviewer/conversation-date-picker.h
index dd177cc..c6e6f26 100644
--- a/logviewer/conversation-date-picker.h
+++ b/logviewer/conversation-date-picker.h
@@ -20,6 +20,11 @@
 #ifndef CONVERSATIONDATEPICKER_H
 #define CONVERSATIONDATEPICKER_H
 
+// workaround for https://bugreports.qt-project.org/browse/QTBUG-22829
+#if defined(Q_MOC_RUN) && !defined(BOOST_TT_HAS_OPERATOR_HPP_INCLUDED)
+#define BOOST_TT_HAS_OPERATOR_HPP_INCLUDED
+#endif
+
 #include <KDatePicker>
 
 #include <TelepathyLoggerQt4/Entity>
diff --git a/logviewer/entity-model.h b/logviewer/entity-model.h
index cd6649e..47050e3 100644
--- a/logviewer/entity-model.h
+++ b/logviewer/entity-model.h
@@ -21,6 +21,11 @@
 #ifndef ENTITYMODEL_H
 #define ENTITYMODEL_H
 
+// workaround for https://bugreports.qt-project.org/browse/QTBUG-22829
+#if defined(Q_MOC_RUN) && !defined(BOOST_TT_HAS_OPERATOR_HPP_INCLUDED)
+#define BOOST_TT_HAS_OPERATOR_HPP_INCLUDED
+#endif
+
 #include <QAbstractListModel>
 
 #include <TelepathyQt/Types>
diff --git a/logviewer/message-view.h b/logviewer/message-view.h
index e4b9b88..e84395f 100644
--- a/logviewer/message-view.h
+++ b/logviewer/message-view.h
@@ -20,6 +20,11 @@
 #ifndef MESSAGEVIEW_H
 #define MESSAGEVIEW_H
 
+// workaround for https://bugreports.qt-project.org/browse/QTBUG-22829
+#if defined(Q_MOC_RUN) && !defined(BOOST_TT_HAS_OPERATOR_HPP_INCLUDED)
+#define BOOST_TT_HAS_OPERATOR_HPP_INCLUDED
+#endif
+
 #include "adium-theme-view.h"
 
 #include <QDate>

自己改下ebuild文件,打上这个补丁就OK了。

另外还有一个问题,就是msn用xmpp方式连接,总是提示network error,无法连接到messenger.live.com,找到http://blogs.fsfe.org/drdanz/?p=632&cpage=1#comment-14534 这篇博客的评论。但是gentoo仓库最新的telepathy-gabble只有0.14.1,还得自己写ebuild,明天再说了。

第一次使用QtTest

之前c++代码用过cppunit,然后开始用gtest,对于qt代码,第一次尝试使用QtTest框架。
要使用QtTest,首先需要在.pro文件中增加配置,让qmake知道要添加qt测试框架:

1
CONFIG += qtestlib

然后,就可以新建一个cpp文件,编写测试代码了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <QtTest/QtTest>

#include "../src/twitterapi.h"

class TwitterApiTest : public QObject
{
    Q_OBJECT
private slots:
    void testRequestToken();
};

void TwitterApiTest::testRequestToken()
{
    Setting s;
    s.twitterUrl = "https://api.twitter.com/1";

    TwitterApi api(&s);
    QSignalSpy spy(&api, SIGNAL(authUrlCreated(bool,QString,Token*)));
    bool result = api.authUrl();

    QVERIFY(result);

    QTest::qWait(10000);

    QVERIFY(spy.isValid());
    QCOMPARE(spy.count(), 1);

    QList<QVariant> arguments = spy.takeFirst();
    QVERIFY(arguments.at(0).toBool() == true);
    qDebug() << arguments.at(1);
}

QTEST_MAIN(TwitterApiTest)

#include "TwitterApiTest.moc"

QtTest相关的函数和宏都在QtTest/QtTest中,直接都include进来即可,我主要用到了里面的QSignalSpy类。
测试类和普通qt类一样,需要继承QObject,所有的测试函数,都声明为私有槽函数(private slots)。这里的测试函数,主要使用了QSignalSpy类,可以用这个类来检测对应的信号是否已经发出,还可以获取信号的参数。QtTest还提供了一些断言宏,具体可以查看assistant中QTest的文档。
每个测试类都需要使用QTEST_MAIN宏生成一个最终的main函数,make之后会生成一个对应的可执行文件。最后,如果测试类声明和实现在一个cpp文件中,需要手工include生成的moc文件。
感觉QtTest没有像cmake中的test那样好的和makefile进行结合,没有增加执行测试的阶段。需要手工运行生成的可执行程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
jinlingjie@babydragon ~/work/aflatoxin-build-desktop-Qt_in_PATH___ $ ./aflatoxin
********* Start testing of TwitterApiTest *********
Config: Using QTest library 4.8.1, Qt 4.8.1
PASS   : TwitterApiTest::initTestCase()
QWARN  : TwitterApiTest::testRequestToken() Don't know how to handle 'Token*', use qRegisterMetaType to register it.
QDEBUG : TwitterApiTest::testRequestToken() oauth head:  "OAuth oauth_callback="-------",oauth_consumer_key="-----------",oauth_nonce="-----------",oauth_signature="-----------",oauth_signature_method="HMAC-SHA1",oauth_timestamp="1333445034",oauth_version="1.0""
QWARN  : TwitterApiTest::testRequestToken() content-type missing in HTTP POST, defaulting to application/octet-stream
QDEBUG : TwitterApiTest::testRequestToken() request token reply: "oauth_token=---------&oauth_token_secret=-----------&oauth_callback_confirmed=true"

QDEBUG : TwitterApiTest::testRequestToken() QVariant(QString, "https://api.twitter.com/oauth/authorize?oauth_token=-------------")
PASS   : TwitterApiTest::testRequestToken()
PASS   : TwitterApiTest::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of TwitterApiTest *********

beamer中使用listings宏包

好几次想在beamer中使用listings宏包来进行格式化和语法高亮,总是编译不通过。终于找到方法了,要在\begin{frame}后面加上[fragile]属性,如果frame包含名字,这个属性要在名字之前,如:

1
2
3
4
5
\begin{frame}[fragile]{title}
\begin{lstlisting}
code
\end{lstlisting}
\end{frame
}

beamer 中使用 tikz 画图和动画

之前使用beamer写slides,都是直接找的图片,所以直接采用贴图的方式。前几天搜索到了tikz这个宏包,发现它可以用来绘制图表,并结合到beamer中形成动画效果,因此找了一些资料,并尝试画简单的流程图。
首先,需要在源文件中指定引用tikz宏包,并且定义一些简单的图形:

1
2
3
4
5
6
\usepackage{tikz}

\tikzstyle{block} = [rectangle, draw, fill=blue!20, text width=4em, text centered, rounded corners]
\tikzstyle{hugeBlock} = [rectangle, draw, fill=blue!20,
    text width=5em, text centered, rounded corners, minimum height=4em
]
\tikzstyle{line
} = [draw, -latex']

这里定义了两个图形,一个是普通的“块”,用蓝色填充,文字居中,有圆角;还有一个“大块”,其他都一样,只是文字宽度和“块”最小宽度不同。
试着画一个简单的静态图:

1
2
3
4
5
6
7
8
9
10
11
12
\begin{frame}{集中式工作流}
    工作流程和svn类似,基本形态如图:
    \begin{tikzpicture}[node distance = 2cm, auto]
        \node [block] (center) {共享仓库};
        \node[block, below of=center, node distance = 3cm](centerDevelop){开发者2};
        \node[block, left of=centerDevelop, node distance = 3cm](leftDevelop){开发者1};
        \node[block, right of=centerDevelop,node distance = 3cm](rightDevelop){开发者3};
        \path[line,<->](center) -- (leftDevelop);
        \path[line,<->](center) -- (centerDevelop);
        \path[line,<->](center) -- node{push/pull} (rightDevelop);
    \end{tikzpicture}
\end{frame
}

首先是开始一个tikzpicture绘图区域,然后使用\node绘制节点,节点都是使用前面定义的“块”。可以通过位置参数来控制绘制的节点和其他节点的相对位置关系。绘制完节点之后,绘制节点的连线:通过\path指令。这里使用双向箭头,所以在参数里面增加了<->符号。连接节点的方式很简单,用前面定义的节点名字,之间用–连起来就好了。这幅图是参照pro git中的集中式工作流介绍中的插图画的,源图为:集中式工作流
绘制出来的图为:

虽然不是太好看,当然也可以通过修改“块”的图形,弄的更好看些。

将tikz用在beamer中,最主要的当然是为了让生成的pdf有“动画”效果了,也就是能够自动生成多张pdf页面,在播放的时候有类似分布展现的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
\begin{frame}\frametitle{集成管理员工作流}\framesubtitle{参与者流程}
    \begin{tikzpicture}[node distance = 3cm, auto]
        \path[use as bounding box] (-1,0) rectangle (10,-2);
        \path[line]<1-> node[block](fock){fock工程};
        \path[line]<2-> node[block, right of=fock, node distance=3cm](clone){克隆到本地}
            (fock) -- (clone);
        \path[line]<3-> node[block, right of=clone, node distance=3cm](edit){修改}
            (clone) -- (edit);
        \path[line]<4-> node[block, below of=edit, node distance=3cm](commit){提交}
            (edit) -- (commit);
        \path[line]<5-> node[block, left of=commit, node distance=3cm](push){推送远程}
            (commit) -- (push);
        \path[line]<6-> node[block, left of=push, node distance=3cm](mergeRequst){请求merge}
            (push) -- (mergeRequst);
    \end{tikzpicture}
\end{frame
}

这里主要靠\path指令,用法和beamer中的itemize/item相同,通过指定<n->,让tex在后面第n页上绘制。需要特别注意的是第一个path。必须要绘制一个bounding box,否则在排版的时候会自动把节点进行居中等重排,导致后面的页面和前面的节点绝对位置有所移动。这里每个path会生成一页,这一个frame会包含6页。

试了下html5的本地存储

这几天在做一个查看统计信息的页面,把选择用的下拉框都做成了通过ajax请求生成。尝试使用html5的本地存储功能,对数据进行缓存,减少对服务器的请求。
用新特性前,当然要检查浏览器是否支持local storage功能了,《html5 up and running》里面介绍了怎么检测,主要就是判断这个dom元素是否存在。网上抄来的检测方法:

1
2
3
if(('localStorage' in window) && window['localStorage'] !== null){
//use local storage
}

使用起来非常方便,因为本地存储是永久性的,所以为了降低和服务器数据不统一的可能性,除了保存数据之外,我还保存了数据的修改时间,每次先取这个时间,如果超过一定间隔,就重新去服务器获取数据,然后更新这个时间。

1
2
var categoryCacheModifyTime = localStorage.getItem("categoryModifyTime");
var categoriesCache = localStorage.getItem("categories");

这里从本地存储中获取指定key的值,需要注意的是,value的类型是string,如果要使用,需要进行类型转换。比如categoriesCache在存入的时候是个json字符串,要变成对象需要:var categories = JSON.parse(categoriesCache)进行转换才行。
把数据放到本地存储里面:

1
2
localStorage.setItem("categories", JSON.stringify(result));
localStorage.setItem("categoryModifyTime", new Date().getTime());

这里也要特别注意,setItem的函数原型是void setItem(DOMString key, DOMString value);所以value也要转换成string才能保存。这里value是个json对象,所以通过JSON.stringify把json对象变成json字符串。

完整的local storage接口定义在:http://dev.w3.org/html5/webstorage/#the-storage-interface 这里。

More Effective C++ 读书笔记六——临时对象

条款19:了解临时对象的来源

c++真正的所谓的临时对象是不可见的——不会再你的源代码中出现。此等匿名对象通常发生于两种情况:一是当隐式类型转换(implicit type conveersions)被施行起来以求函数调用能够成功;二是当函数返回对象的时候。

第一种情况的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

class Int
{
public:
        Int(int value) {
                _value = value;
                std::cout << "In constructor, count: " << ++count << std::endl;
        }
        ~Int() {
                std::cout << "In destructor\n";
        }

        int _value;
private:
        static int count;
};

int Int::count = 0;

void printInt(Int intValue) {
        std::cout << "int value is: " << intValue._value << std::endl;
}

int main()
{
        printInt(10);
        return 0;
}

这个例子的运行结果是:
In constructor, count: 1
int value is: 10
In destructor
验证了编译器再发现没法调用printInt(int)的时候,会进行自动类型转换,通过Int的构造函数,将int隐式转换成Int(这个貌似之前也提到过了)。这个构造出来的就是所谓的临时对象,从输出的顺序看,这个对象在函数返回之后被析构。

只有当对象以by value(传值)方式传递,或是当对象被传递给一个reference-to -const参数时,这些转换才会发生。如果参数被传递给一个reference-to-non-const参数,并不会发生此类转换

也就是说,把上面的printInt入参改成Int&的时候,就无法调用成功。原因很简单了,这个临时对象,如果没修改,函数返回之后也被析构了,没法再次获取到,即使允许也是没意义的。改成Int&之后,编译器(g++)有这样的报错:
tmp.cpp: 在函数‘int main()’中:
tmp.cpp:27:13: 错误:用类型为‘int’的右值初始化类型为‘Int&’的非常量引用无效
tmp.cpp:21:6: 错误:在传递‘void printInt(Int&)’的第 1 个实参时

条款21:利用重载(overload)避免隐式类型转换(implicit type conversions)

这里介绍的就是通过重载,避免编译器自动通过构造函数创建临时对象,但是重载太多后面的维护什么的成本也会上升,需要考虑这个的必要性。

每个“重载操作符”必须获得至少一个“用户定制类型”的自变量

条款20:协助完成返回值优化(RVO)

另一个容易产生临时对象的地方就是函数返回值。不过经过尝试,发现目前编译器(g++)可以把命名变量也通过返回值优化去除。
也就是说,这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>

class Int
{
public:
        Int(int value) {
                _value = value;
                std::cout << "In constructor, count: " << ++count << " value: " << _value <<std::endl;
        }
        Int(const Int &rhs) {
                _value = rhs._value;
                std::cout << "In copy constructor, count: " << ++count << " value: " << _value << std::endl;
        }


        friend const Int operator+(const Int &lhs, const Int &rhs);

private:
        int _value;
        static int count;
};

int Int::count = 0;
const Int operator+(const Int &lhs, const Int &rhs) {
        Int result(lhs._value + rhs._value);
        return result;
}

int main()
{
        Int a(1);
        Int b(2);
        Int c = a + b;

        return 0;
}

和这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>

class Int
{
public:
        Int(int value) {
                _value = value;
                std::cout << "In constructor, count: " << ++count << " value: " << _value <<std::endl;
        }
        Int(const Int &rhs) {
                _value = rhs._value;
                std::cout << "In copy constructor, count: " << ++count << " value: " << _value << std::endl;
        }


        friend const Int operator+(const Int &lhs, const Int &rhs);

private:
        int _value;
        static int count;
};

int Int::count = 0;
const Int operator+(const Int &lhs, const Int &rhs) {
        return Int(lhs._value + rhs._value);
}

int main()
{
        Int a(1);
        Int b(2);
        Int c = a + b;

        return 0;
}

最终的执行结果都是一样的:
In constructor, count: 1 value: 1
In constructor, count: 2 value: 2
In constructor, count: 3 value: 3
没有因为前者在operator+中多了一个result对象而多一个临时变量。

gentoo libvirt kvm cgroup目录找不到问题

这段时间发现虚拟机只能启动一次,如果再次启动就会提示:Unable to create cgroup for $VM_NAME

刚开始以为是cgourp没有挂载,但是通过mount命令发现cgroup已经挂载。最后在论坛上查到了原因:
/lib64/rc/sh/cgroup-release-agent.sh这个文件里面有清理cgroup目录的行为:
if [ -d ${cgroup}/$1 ]; then
      rmdir ${cgroup}/$1
fi
最暴力的解决方法是直接改了这个文件,增加qemu的判断:
if [ "$1" != "qemu"] ; then
        if [ -d ${cgroup}/$1 ]; then
                rmdir ${cgroup}/$1
        fi
fi
然后kvm就OK了。

More Effective C++ 读书笔记五——异常

条款12:了解“抛出一个exception”与“传递一个参数”或“调用一个虚函数”之间的差异

第一,exception object总是会被复制,如果以by value方式捕捉,它们甚至被复制两次。至于传递给函数参数的对象不一定得复制。第二,“被抛出成为exceptions”的对象,其被允许的类型转换动作,比“被传递到函数去”的对象少。第三,catch子句以其“出现于源代码的顺序”被编译器检查比对,其中第一个匹配成功者便被执行;而当我们以某个对象调用一个虚函数,被选中执行的是那个“与对象类型最佳吻合”的函数。

条款13:以by reference方式捕捉exceptions

上面两个条款都提到了被抛出对象的复制问题,通过下面的几个例子试了下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

static int count = 0;

class e_class {
public:
        e_class() {
                std::cout << "in constructor: count: " << ++count << std::endl;
        }
        e_class(const e_class &right) {
                std::cout << "in copy constructor: count: " << ++count << std::endl;
        }
};

int main(){
        try {
                e_class e_c; //创建对象,调用构造函数count累加
                throw e_c; //抛出异常,e_c被复制,调用拷贝构造函数,count累加
        } catch (e_class e) { //以by value形式catch,对象再次被复制,count累加
        }

        return 0;
}

这里count被加了3次,第一次是e_class对象构造的时候,然后抛出去的时候,对象被复制,然后catch的时候因为按值传递,传入对象再次被复制。执行结果为:
in constructor: count: 1
in copy constructor: count: 2
in copy constructor: count: 3
这里能被避免的就是catch这里,如果按照引用传递,这次复制就可以避免:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

static int count = 0;

class e_class {
public:
        e_class() {
                std::cout << "in constructor: count: " << ++count << std::endl;
        }
        e_class(const e_class &right) {
                std::cout << "in copy constructor: count: " << ++count << std::endl;
        }
};

int main(){
        try {
                e_class e_c; //创建对象,调用构造函数count累加
                throw e_c; //抛出异常,e_c被复制,调用拷贝构造函数,count累加
        } catch (e_class &e) { //以by reference形式catch,不会再次被复制
        }

        return 0;
}

采用按引用方式catch后,和函数按引用传递参数一样,对象不再被复制,执行结果为:
in constructor: count: 1
in copy constructor: count: 2

条款12中还介绍了再次抛出异常的对象复制问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

static int count = 0;

class e_class {
        public:
                e_class() {
                        std::cout << "in constructor: count: " << ++count << std::endl;
                }
                e_class(const e_class &right) {
                        std::cout << "in copy constructor: count: " << ++count << std::endl;
                }
};

int main(){
        try {
                try {
                        e_class e_c;
                        throw e_c;
                } catch (e_class &e) {
                        throw e;  //这里抛出的是e的副本,会对e进行复制后抛出
                }
        }catch(...) {
        }

        return 0;
}

这里在catch子句中重新抛出了异常,但是这种方式抛出的其实是异常对象的副本,对象会再次被复制,执行结果为:
in constructor: count: 1
in copy constructor: count: 2
in copy constructor: count: 3
如果改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

static int count = 0;

class e_class {
        public:
                e_class() {
                        std::cout << "in constructor: count: " << ++count << std::endl;
                }
                e_class(const e_class &right) {
                        std::cout << "in copy constructor: count: " << ++count << std::endl;
                }
};

int main(){
        try {
                try {
                        e_class e_c;
                        throw e_c;
                } catch (e_class &e) {
                        throw;  //直接抛出e,不会进行复制
                }
        }catch(...) {
        }

        return 0;
}

这里throw语句不带任何参数,抛出的是当前异常本身,当前的异常对象不会再次被复制,执行结果为:
in constructor: count: 1
in copy constructor: count: 2