About
RSS

Bit Focus


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++ 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

C++ 转移构造

    这篇文章中的例子均可在 GCC 4.5.1 版本和 Clang 2.8 版本编译, 在某些较老或优化不尽相同的编译器上, 下面的例程可能并不能获得与文中所述一致的结果.
    不过, 这篇文章的重点并非讲述 C++ 中关于复制构造函数的优化, 而是为了说明 C++0x 标准中出现的 Move Semantic 所试图解决的问题之一, 所以对关于复制构造函数的例子有大致理解即可.
    重点是, 如果希望完整地编译文中最后一部分中出现的任何 0x 标准相关的代码, 强烈推荐 GCC 4.5.x 版本编译器.

    废话少说, 先来一段代码:
#include <iostream>

struct cp_ctor_doubling {
    cp_ctor_doubling(int ii)
        : i(ii)
    {}

    cp_ctor_doubling(cp_ctor_doubling const& rhs)
        : i(rhs.i * 2)
    {}

    int const i;
};

cp_ctor_doubling create(int i)
{
    return cp_ctor_doubling(i);
}

int main()
{
    cp_ctor_doubling c(create(10));
    std::cout << c.i << std::endl;
    return 0;
}
问: 输出多少? A. 10 B. 20 C. 40 D. 以上答案都不正确.
    嗯, 也许有得到 B 和 C 的, 得到 D 的同学也许该试着为自己的编译器报个 bug 了, 如果得到 A, 那么恭喜, 您的编译器已经具备对于复制构造函数最基本的优化能力了.
    C 语言忠实信徒揶揄 C++ 编译器会背着程序员做的 "额外的事情" 时, 历来会想到 C++ 的类复制构造函数. 然而, 现实不仅仅是这样, 编译器还会做出更多额外的事情, 比如把不必要的复制构造函数调用优化掉, 或者更准确地说, 把一些临时对象给优化掉, 于是复制构造函数根本不会被调用到.
    如果对上述例子中的编译优化有任何疑虑, 可以尝试下面这段代码
#include <iostream>

struct cp_ctor_not_impl {
    cp_ctor_not_impl(int ii)
        : i(ii)
    {}

    cp_ctor_not_impl(cp_ctor_not_impl const&);

    int const i;
};

cp_ctor_not_impl create(int i)
{
    return cp_ctor_not_impl(i);
}

int main()
{
    cp_ctor_not_impl c(create(10));
    std::cout << c.i << std::endl;
    return 0;
}

Permanent Link: /p/94 Load full text

Post tags:

 C++
 C++11
 Move Semantic
 Copy Constructor


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