C++ 2011 中调用对象的 Rvalue Reference 成员函数重载
之前提到的 rvalue reference 还有一点没有结算, 其实本来早就想写完它, 但是尝试了很久, 最后 到 stackoverflow 上问来问去才最终解决, 那就是当某个对象是 rvalue 时, 怎么调用 rvalue 的成员函数重载. 之前在有关 rvalue 函数重载的讨论中说到参数是 rvalue 的情况, 很自然地, 如果认为 (简单起见, 非 virtual 的) 对象成员函数等价于普通函数加上一个 this 参数, 那么如何分辨 this 参数是 left value 还是 rvalue 呢? 受到对象成员函数可以 仅根据 const 修饰与否来重载这个现状的启发, 对于对象本身是 rvalue 的重载当然很可能是为成员函数加上某种修饰, 类似下面这个样子 struct Type { void func(); // for normal left reference void func() const; // for const left reference void func() rvalue-qualified-id; // for rvalue reference };
想法当然好, 而且确实有这么一回事, 只是标准敢正常一点么? 下面上真实代码, 这段代码目前似乎只能在 clang++ (我试验的版本是 2.9) 上编译通过 #include <cstdio>
struct Type { Type() {}
void func() & { puts("a"); }
void func() const& { puts("b"); }
void func() && { puts("c"); } };
int main() { Type a; a.func();
Type const b; b.func();
Type().func();
return 0; }
输出是 a b c
前两个函数重载看起来很怪异? 原因是, 如果成员函数重载中只要有一个函数是给 rvalue 用的 (也就是函数后面有 && 修饰), 那么其它重载也必须标注上一个 & 号. 读的时候去掉前两个重载的 & 就很容易看明白了. 而最后一个重载正是给 rvalue 用的. 这样变态地使用符号, 看来标准委员会看来是毫不介意 C++ 逐渐演化成既不表意又不象形的新世纪面向对象强化版 Brainfuck. 真的在代码中写这种东西的死 coder 是伤不起的!
Posted at May 01 2011 - 09:15:20
Permanent Link:
/p/303
Load full text
|
Post tags:
C++
Function Overload
Right Reference
C++11
Left Value
|
C++ Howto: 在栈上构造一个对象
C++ 2011 标准引入的各种符号让我感到 C++ 已经快 变成一门妖术了. 考虑到很多不明真相的 C 程序员同学们觉得 C++ 妖术是 C with classes, 那么这里先介绍一下 C++ 魔法大典中最基本东西: 怎么运用编译器的魔力在栈上召唤一个对象? 假设现在有个类叫做 type , 它有个 无参数的构造函数, 那么下面的代码 type object(); 是巫师学徒们常犯的错误, 这个语句实际上是声明了一个叫做 object 的函数, 它返回一个 type 对象. 施展无参构造魔法的正确咒语是 type object; 此外, 还有一个很吐血的咒术是 type(object); 同样是定义一个名字为 object 类型为 type 的对象. 是否很震惊? 其实这里的括号是可以任意加的, 如 type object; type(object); type((object));
它们的效果相同. 如果它们写在一起, 后边的会跟第一个发生定义冲突. 如果现在它有个构造函数需要一个 int 类型参数, 那么下面这两种代码 type object = 0; type object = type(0);
是不正确的, 哦, 不, 至少是不严谨的, 而且看起来就是土气的 C 程序++, 而不是 C++ 魔法, 正确的咒语是 type object(0); 因为当 type 的构造函数是如下定义时第一种写法是编不过去的 struct type { explicit type(int); };
而第二种写法确实读起来更象是 "调用构造函数, 初始化对象" 两步, 不过它实际上要用到复制构造函数, 虽然很可能编译器有足够强大的魔力优化掉这一次对象复制, 但如果把复制构造函数 delete 掉或者置于 private 或 protected 封印之下, 编译器会给出一个恼人的错误. PS: 在 The C++ Programming Language (C++ 程序设计语言) 一书中, 11.3.3 中有提到一个例子 ... For example, complex b = 3; means complex b = complex(3); 我认为这一处是错误的, 原因如上所说. 好, 现在再回到刚才那个括号打水漂的定义方式, 下面这种写法 int a = 3; type(a);
后面一句是在召唤一个临时的 type 对象么? 当然不是, 编一下就会知道, 那仍然是在定义一个名字为 a 的对象, 即使 type 没有无参构造函数.
Posted at May 01 2011 - 09:05:45
Permanent Link:
/p/292
Load full text
|
Post tags:
C++
Constructor
Copy Constructor
|
C++ 异常不怎么常见知识点 --- 后传
上一篇后 timothyqiu 同学回复提到通过 std::set_unexpected 指定一个处理方法, 在函数抛出与异常声明不相符的异常时调用. 比如下面这样 #include <stdexcept> #include <iostream>
void func_throws() throw (std::out_of_range) { throw std::logic_error("danger!"); }
void throw_another() { throw std::out_of_range("safe"); }
int main() { std::set_unexpected(throw_another); try { func_throws(); } catch (std::out_of_range e) { std::cerr << e.what() << std::endl; } return 0; }
有了这个处理函数后, 确实异常被神奇般地抓住了. 不过换个写法, 如果处理函数里面也继续抛 std::logic_error #include <stdexcept> #include <iostream>
void func_throws() throw (std::out_of_range) { throw std::logic_error("danger!"); }
void throw_another() { throw std::logic_error("not safe!"); }
int main() { std::set_unexpected(throw_another); try { func_throws(); } catch (std::out_of_range e) { std::cerr << e.what() << std::endl; } return 0; }
程序照崩. 也就是说, 处理函数相当于替换原来的函数, 在错误处原地重新抛出一个异常, 但异常必须匹配原来函数的异常声明, 否则异常仍然无法捕捉. 明显扯淡么, 随便找两个异常声明不搭界的函数, 这货不就不管用了么, 总不能随抛随设吧. 当然, 标准还规定了一个很犀利的异常类型, 叫做 std::bad_exception , 如果把这家伙加进异常声明, 那就给力了 #include <stdexcept> #include <iostream>
void func_throws() throw (std::out_of_range, std::bad_exception) { throw std::logic_error("danger!"); }
void throw_another() { throw; }
int main() { std::set_unexpected(throw_another); try { func_throws(); } catch (std::bad_exception e) { std::cerr << e.what() << std::endl; } return 0; }
好了, 原来的异常在 throw_another 里面摇身一变, 成了 std::bad_exception , 不过看 std::cerr 吐出来的东西, 信息都消失了啊, 这不是坑爹么. 其实说到底, 这跟写成 void throw_another() { throw std::bad_exception("std::bad_exception"); }
Posted at Apr 14 2011 - 14:35:07
Permanent Link:
/p/275
Load full text
|
Post tags:
C++
Exception Handling
|
C++ 异常不怎么常见知识点
之前也反复有人推荐 Google 的 C++ Coding Style, 昨天看到一位老师写的 解读 Google C++ code style 谈对 C++ 的理解, 文后的讨论大多集中在 Google 不允许 C++ 异常. 我这里就不多插嘴了, 不过有些关于异常的基本情况还是想略聊一聊. 文中的例子使用 g++ 4.5.2 和 clang++ 2.9 编译测试通过. 如果一个函数声明了异常声明与该函数实际抛出的异常不匹配, 那么这个异常 不会被捕获到. 也就是说 catch (...) 不是银弹. #include <stdexcept>
void func_throws() throw (std::out_of_range) { throw std::logic_error("danger!"); }
int main() { try { func_throws(); } catch (std::out_of_range) {} catch (std::logic_error) {} catch (...) {} return 0; }
补丁: std::set_unexpected 函数与不预期的异常 而一个有趣的 C++ 规定是, 如果一个函数没有任何异常声明, 则该函数有可能抛出 任何异常. 这个规定的一个隐喻是, 一切 C 函数都有可能抛出任何异常. 虽然 C 连异常是什么都不知道, 不过没关系. 因为 C 函数是 异常透明的. 比如下面的例子 #include <stdexcept>
extern "C" void exception_pass();
void func_throws() { throw std::logic_error("danger!"); }
void upper_cpp_func() { exception_pass(); }
int main() { try { upper_cpp_func(); } catch (std::logic_error) { return 0; } return 1; }
extern "C" void exception_pass() { func_throws(); }
在异常发生后的 stack unwind 阶段, C 函数调用栈帧与 C++ 函数栈帧的处理方式相同. 执行结果是一样的. 但这并不意味着一种好的 C/C++ 编程方式, C 是没有 RAII 的! 另外, 分离编译再链接时行为会不同, 可能出现异常无法被捕获的情况.
Posted at Apr 11 2011 - 13:42:28
Permanent Link:
/p/256
Load full text
|
Post tags:
Exception Handling
C
C++
|
C++ 2011 中的 Left Value 与 Rvalue Reference
#include "某 C++ 概念之序篇" 答: std::stringstream() 产生的临时变量无法匹配类型 std::stringstream& . 但是 std::stringstream() 可以匹配类型 std::stringstream const& , 也就是说, 如果函数 std::stringstream& operator<<(std::stringstream& os, double d) 的签名和实现作修改成下面这个样子就行了 std::stringstream const& operator<<(std::stringstream const& os, double d) { const_cast<std::stringstream&>(os).operator<<(d); return os; }
之前扯过 C++ 已经没有语法意义上的左值了, 不过这里似乎有一点点很微妙的, 跟左值很类似的东西, 那就是, C++ 引用. 任何一本 C++ 入门书籍里面都会花一些篇幅来讨论引用, 其基本意义, 无非是 某个东西的别名, 因此, 这 某个东西对于引用而言显然是至关重要的. 最基本的一点是, 如果被引用的东西是临时变量 (顺便一提, 如果是字面常量这种比较尴尬的情况, 视同临时变量) 怎么办? 比如之前代码样例 A 中的情况? 这就牵扯到 C++ 中正统的 left value. 因为 left 已经不再有任何 "左" 相关的意义的, 所以我不太想在本文中把这货称之为 "左值" (实际上英文在这些方面更加灵活, 比如 Copyleft 这词跟 left 也没太大关系, 用来吐槽则非常不错). 一个东西如果是 left value, 那么它就可以被赋值给 left value 引用, 所谓 left value 引用嘛, 就是就是类似 std::stringstream& 的引用类型, 而暗含 const 的类型的引用则不算 typedef int const int_const; int_const& this_is_not_a_left_value_reference;
那究竟什么是 left value 呢? 一个语句之后仍然存在的变量, 表达式, 或者这样类似的 "具有 值属性的什么东西" 就是 left value. 而 std::cout << (std::stringstream() << d).str().length() << std::endl; 这句之后, 显然临时构造的 std::stringstream() 就被析构了, 因此这家伙不算是 left value, 自然也就无法被 std::stringstream& 所引用. 那常数为什么不算 left value 呢? 当然, 可以认为常数居阴阳之外, 别谈语句之后, 吐核退出, 就算是宇宙毁灭, 什么 π 啊 e 啊还活得好好的; 不过, 从计算机的角度来理解, 常数是 CPU 指令的立即数, 所以别谈语句之后, 只要这条指令结束, 它就没了. 然而, C++ 一个很邪恶的规定是, 即使一个东西不是 left value, 它仍然可以绑定 const 引用, 比如以下语句都是合法的 std::pair<int, int> const& pair = std::make_pair(0, 1); std::stringstream const& strm = std::stringstream(); std::string const& x = "Raki suta"; int const& i = 0;
这样直白地写出来是有点让人觉得不舒服, 不过作为函数调用的参数则好得多, 比如之前的代码片段 B 那样
Posted at Mar 28 2011 - 13:07:31
Permanent Link:
/p/232
Load full text
|
Post tags:
Move Semantic
Operator Overload
C++
Right Reference
Copy Constructor
Left Value
C++11
|
某 C++ 概念之序篇
C++ 虔诚的信徒们, 请阅读下面的代码, 回答码后的问题 /* 完整样例 A */ #include <iostream> #include <sstream>
std::stringstream& operator<<(std::stringstream& os, double d) { os.operator<<(d); return os; }
int main() { double d; std::cin >> d; std::cout << (std::stringstream() << d).str().length() << std::endl; return 0; }
问: 为什么上述代码无法通过编译? /* 代码片段 B */ #include <map> #include <string> #include <stdexcept>
struct key_error : public std::logic_error { explicit key_error(std::string const& what) : std::logic_error(what) {} };
std::string const& lookup(std::map<std::string, std::string> const& map , std::string const& key) throw(key_error) { std::map<std::string, std::string>::const_iterator i = map.find(key); if (map.end() == i) { throw key_error("key ``" + key + "'' not found in map"); } return i->second; }
问: 上述代码中的 lookup 函数有什么隐患? 答案已揭晓, 点此查看, 谢谢关注.
Posted at Mar 26 2011 - 09:37:59
Permanent Link:
/p/222
Load full text
|
Post tags:
C++11
Copy Constructor
Move Semantic
C++
Operator Overload
Left Value
Right Reference
|
Qt 囧形: QRect
Qt 中的整点矩形类 QRect 的表示很独特, 存放的是左上角坐标和右下角坐标 (准确地说, 是 int x1; int y1; int x2; int y2; ). 对 QRect 取上下左右都是闭区间上的边界, 比如 QRect rect(QPoint(0, 2), QSize(8, 8)); rect.left(); // 0 rect.right(); // 7 : 0 + 8 - 1 rect.top(); // 2 rect.bottom(); // 9 : 2 + 8 - 1
其实也没什么大问题, 因为确实就是这样. 但是, 自古以来就被训练成左开右闭思维的键盘打击手们, 一不小心就会出错, 比如下面的 Python 列表切片代码示例, 取一个列表的一半, 后一半 list = range(0, 8) list[0: len(list) / 2] // [0, 1, 2, 3] list[len(list) / 2: len(list)] // [4, 5, 6, 7]
而如果给 QRect "切片", 求一个矩形的左半边和右半边, 代码则看起来像 QRect rect(QPoint(0, 2), QSize(8, 8));
QRect leftHalf(rect); leftHalf.setRight(leftHalf.left() + leftHalf.width() / 2 - 1);
QRect rightHalf(rect); rightHalf.setLeft(rightHalf.left() + rightHalf.width() / 2);
设置右边界的代码修正这个 -1 看起来非常非常不自然. 而在实际运用中, 经常会遇到各种 +1 -1 的修正. 至于 Nokia 的开发者们自己是不是搞清楚了 QRect 我也不太清楚, 不过 QPainter::drawRect(QRect const&) 这个函数是看起来有 bug 的样子, 比如下面这个例子 #include <QApplication> #include <QToolButton> #include <QBrush> #include <QPainter>
struct TestButton : public QToolButton { void paintEvent(QPaintEvent*) { QPainter painter(this); painter.setBrush(Qt::NoBrush);
painter.setPen(QPen(Qt::white, 1)); painter.drawRect(rect().adjusted(3, 3, -3, -3));
painter.setPen(QPen(Qt::blue, 1)); painter.drawRect(rect().adjusted(2, 2, -2, -2));
painter.setPen(QPen(Qt::green, 1)); painter.drawRect(rect().adjusted(1, 1, -1, -1));
painter.setPen(QPen(Qt::red, 1)); painter.drawRect(rect()); } };
int main(int argc, char* argv[]) { QApplication app(argc, argv); TestButton button; button.show(); return app.exec(); }
放大了看, 最外侧红框的右边和下边都不见了.
Posted at Mar 26 2011 - 09:11:12
Permanent Link:
/p/219
Load full text
|
Post tags:
Qt
QRect
C++
|
拿去吧, 四字节整数类型
对于 C(++) 之流在平台相关这种水深火热之中挣扎的语言, 对机器字长, 以及整数类型字长在一些情况下是极其敏感的. 所以每个大型项目建设之前, 在某个头文件里面定义一大堆 int32 或者是 unsigned8_t 之类的固定字长类型显得非常必要, 而且让工程看起来很专业的样子. 然而, 由于标准库中没有, 项目之间又互相不信任, 结果是几乎每个库都会自定一套, 比如 boost 就有 int8_t , int_least8_t 等类型, 而 Qt 更是拿出了叫做 qulonglong 的类型来卖萌. 虽然说是跨平台, 但是如果迁移平台时使用了错误版本的库呢? 因此有时依然需要写一个类似下面的程序来人肉验证 int main() { std::cout << sizeof(int4_type) << " " << sizeof(int8_type) << std::endl; return 0; }
然而, 人都是不可靠的, 如果有人对你说, 喔, 我跑了上面的程序, 结果是 "4 8", 也许你仍会强迫症一样, 自己再验证一次. 好了, 也许你已经厌烦了, 这么一点破事情, 难道不能交给机器做么? 理想的解决方案是, 只需要这样一个模板就好了 template <int _SizeOf> struct IWantAnIntegralTypeOf;
int main() { IWantAnIntegralTypeOf<4> this_is_a_4_bytes_int; IWantAnIntegralTypeOf<8> this_is_a_8_bytes_int; return 0; }
不是吗? 这是完全可行的, 实际上, 可以把 C 支持的所有内建整数类型先拿出来, 组成一个类型链表来搜索, 像下面这样 struct __null__ {};
struct c_char { typedef char type; typedef __null__ next; };
struct c_short { typedef short type; typedef c_char next; };
struct c_int { typedef int type; typedef c_short next; };
struct c_long { typedef long type; typedef c_int next; };
struct c_long_long { typedef long long type; typedef c_long next; };
struct __head__ { typedef c_long_long next; };
接下来, 弄个模板来搜, 搜索当然是用模板偏特化的方法 template <typename _TypeNode, int _SizeOf, bool _SizeMatched> struct type_finder;
template <typename _TypeNode, int _SizeOf> struct type_finder<_TypeNode, _SizeOf, true> { typedef typename _TypeNode::type type; };
template <typename _TypeNode, int _SizeOf> struct type_finder<_TypeNode, _SizeOf, false> { typedef typename type_finder< typename _TypeNode::next , _SizeOf , _SizeOf == sizeof(typename _TypeNode::next::type)>::type type; };
Posted at Mar 26 2011 - 09:08:36
Permanent Link:
/p/217
Load full text
|
Post tags:
Generic Programming
Metaprogramming
C++
Template
|
0
1
Page 2
3
4
|