About
RSS

Bit Focus


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 是伤不起的!

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 掉或者置于 privateprotected 封印之下, 编译器会给出一个恼人的错误.

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 没有无参构造函数.

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");
}

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 的!
    另外, 分离编译再链接时行为会不同, 可能出现异常无法被捕获的情况.

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 那样

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 函数有什么隐患?

答案已揭晓, 点此查看, 谢谢关注.

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();
}
    放大了看, 最外侧红框的右边和下边都不见了.

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;
};

Permanent Link: /p/217 Load full text

Post tags:

 Generic Programming
 Metaprogramming
 C++
 Template

0 1 Page 2 3 4


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site