STEINS;GATE 0 印象

简单发表一下通 SG0 的感想吧。(前方有剧透

个人感觉嘟嘟噜和真帆是最出彩的两条线。原来我对真有理不感冒,然而 SG0 让我喜欢上了这个角色。在真有理的路线中,她得知为了救自己,红莉栖被牺牲了,然后便毅然踏上了回到过去说服凶真的时间旅行。这封给凶真的信被缓缓念出的时候,我真的感觉有点想哭:

真有理并不是一个只会说嘟嘟噜的傻乎乎的女高中生,为了重要的人,她也会站出来。SG0 让人看到了她有勇气的一面,这是 SG 所没有展现的。

另一条线是真帆线。这条线和凶真关系不大,重点描绘了真帆和红莉栖作为研究所的前后辈之间的羁绊。同为天才少女,因为一首 K331 而真正熟识。红莉栖尊敬真帆,真帆却一度为自己比不上红莉栖而感到沮丧。萨列里和莫扎特,谣言并不真实,最后当误会被解开,真帆才意识到在红莉栖眼里自己也是重要的存在,可惜那时不论本人还是 Amadeus 都已经消失了。另外,莫扎特能让人集中注意力这点,我也算是很有共鸣了(写代码的时候听谁的歌比较好? - laike9m的回答 )。

教授和篝线中规中矩。

我不满意的是助手线。首先这条线太长了,打的时候一直在想怎么还没完。然后是回到 α 线那段简直莫名其妙,按理说这两条世界线的切换算是很大的事了,之后却几乎没有任何交待。这个插曲,给人感觉完全是为了让凶真再见一次红莉栖而设计的,非常突兀,仿佛在提醒玩家这是助手线。再就是,一个我认为可以展开好好描述的情节却又一笔带过了,说的是凶真从 2036 年经过 3000 次跳跃回到 2011 年那段。这段的感觉就是,胸针说我要跳了,3000 次!天哪好悲壮,中间到底会经历什么,是疲惫不堪精神崩溃,还是战火中游走在死亡边缘,感觉剧情高潮要来了。正期待着,突然就告诉你凶真跳回去了,中间发生了什么,你自己去想吧。卧槽,搞笑呢吧。日常剧情做一堆,关键剧情一笔带过,这算什么事!就是放几个静止画面概括一下也比现在要好。

还有个更大的问题,那就是篝这个角色。我不理解这个角色的设计。走散失忆洗脑啥的倒都还好,和真有理的母女羁绊描绘得也不错,问题是长相。输入红莉栖的记忆勉强还可以解释为为了套出时间机器的情报,但为什么篝要设计成长得和红莉栖一样啊?意义何在?如果玩家想回忆助手,看 Amadeus 就好了啊,反正随时随地都可以聊天,甚至真帆线里还有 Amadeus 也有 RS 的猜想,这点也可以展开啊。让篝长得像红莉栖,玩家就会把她视作替代品了吗?不会。基于此,这个角色我认为是失败的。而且,之前一直以为会爆出什么惊天秘密,比如篝是红莉栖的克隆什么的,也没有,长相完全一致这件事最后竟这么不了了之了,我都怀疑是不是忘写了。

对几条线的看法大概就这样。TE 没啥好说的中规中矩,感觉没做完(接真有理回到未来没讲),估计要么还有新作要么在动画里补完。就个人而言,我最 enjoy 的部分其实是由纪和铃羽,以及真有理和篝的互动,这种桥段真的是百看不厌(虽然好像和铃羽互动最多的那个由纪其实是篝假扮的)。对了,听真帆介绍 Frame problem 也是极有意思的。游戏的评价就先写到这,至于动画,似乎是把各条线糅合在一起讲,祈祷它能接近 SG 动画的高度吧。

Revisiting Move Semantics(and all the related idioms)

While I was learning move semantics, I always had a feeling, that even though I knew the concept quite well, I cannot fit it into the big picture of C++. Move semantics is not like some syntactic sugar that solely exists for convenience, it deeply affected the way people think and write C++ and has become one of the most important C++ idioms. But hey, the pond of C++ was already full of other idioms, when you throw move semantics in, mutual extrusion comes with it. Did move semantics break, enhance or replace other idioms? I don't know, but I want to find out.

Value Semantics

Value semantics is what makes me start to think about this problem. Since there aren't many things in C++ with the name "semantics", I naturally thought, "maybe value and move semantics have some connections?". Turns out, it's not just connections, it's the origin:

Move semantics is not an attempt to supplant copy semantics, nor undermine it in any way. Rather this proposal seeks to augment copy semantics.

- Move Semantics Proposal, September 10, 2002

Perhaps you've noticed it uses the wording "copy semantics", in fact, "value semantics" and "copy semantics" are the same thing, and I'll use them interchangeably.

OK, so what is value semantics? isocpp has a whole page talking about it, but basically, value semantics means assignment copies the value, like T b = a;. That's the definition, but often times value semantics just means to create, use, store the object itself, pass, return by value, rather than pointers or references.

The opposite concept is reference semantics, where assignment copies the pointer. In reference semantics, what's important is identity, for example T& b = a; , we have to remember that b is an alias of a, not anything else. But in value semantics, we don't care about identity at all, we only care about the value an object1 holds. This is brought by the nature of copy, because a copy is ensured to give us two independent objects that hold the same value, you can't tell which one is the source, nor does it affect usage.

Unlike other languages(Java, C#, JavaScript), C++ is built on value semantics. By default, assignment does bit-wise-copy(if no user-defined copy ctor is involved), arguments and return values are copy-constructed(yes I know there's RVO). Keeping value semantics is considered a good thing in C++. On the one hand, it's safer, because you don't need to worry about dangling pointers and all the creepy stuff; on the other hand, it's faster, because you have less indirection, see here for the official explanation.

Move Semantics: V8 Engine on the Value Semantics Car

Move semantics is not an attempt to supplant copy semantics. They are totally compatible with each other. I came up with this metaphor which I feel describes their relation really well.

Imagine you have a car, it ran smoothly with the built-in engine. One day, you installed an extra V8 engine onto this car. Whenever you have enough fuel, the V8 engine is able to speed up your car, and that makes you happy.

So, the car is value semantics, and the V8 engine is move semantics. Installing an engine on your car does not require a new car, it's still the same car, just like using move semantics won't make you drop value semantics, because you're still operating on the object itself not its references or pointers. Further more, the move if you can, else copy strategy, implemented by the binding preferences, is exactly like way engine is chosen, that is to use V8 if you can(enough fuel), otherwise fall back to the original engine.

Now we have a pretty good understanding of Howard Hinnant(main author of the move proposal)'s answer on SO:

Move semantics allows us to keep value semantics, but at the same time gain the performance of reference semantics in those cases where the value of the original (copied-from) object is unimportant to program logic.

EDIT: Howard added some comment that really worth mentioning. By definition, move semantics acts more like reference semantics, because the moved-to and moved-from objects are not independent, when modifying(either by move-construction or move-assignment) the moved-to object, the moved-from object is also modified. However, it doesn't really matter——when move semantics takes place, you don't care about the moved-from object, it's either a pure rvalue (so nobody else has a reference to the original), or when the programmer specifically says "I don't care about the value of the original after the copy" (by using std::move instead of copy). Since modification to the original object has no impact on the program, you can use the moved-to object as if it's an independent copy, retaining the appearance of value semantics.

Move Semantics and Performance Optimization

Move semantics is mostly about performance optimization: the ability to move an expensive object from one address in memory to another, while pilfering resources of the source in order to construct the target with minimum expense.

- Move Semantics Proposal

As stated in the proposal, the main benefit people get from move semantics are performance boost. I'll give two examples here.

The optimization you can see

Suppose we have a handler(whatever that is) which is expensive to construct, and we want to store it into a map for future use.

std::unordered_map<string, Handler> handlers;
void RegisterHandler(const string& name, Handler handler) {
  handlers[name] = std::move(handler);
}
RegisterHandler("handler-A", build_handler());

This is a typical use of move, and of course it assumes Handler has a move ctor. By moving(not copying)-constructing a map value, a lot of time may be saved.

The optimization you can't see

Howard Hinnant once mentioned in his talk that, the idea of move semantics came from optimizing std::vector. How?

A std::vector<T> object is basically a set of pointers to an internal data buffer on heap, like begin() and end(). Copying a vector is expensive due to allocating new memory for the data buffer. When move is used instead of copy, only the pointers get copied and point to the old buffer.

What's more, move also boosts vector insert operation. This is explained in the vector Example section in the proposal. Say we have a std::vector<string> with two elements "AAAAA" and "BBBBB", now we want to insert "CCCCC" at index 1. Assuming the vector has enough capacity, the following graph demonstrates the process of inserting with copy vs move.

Everything shown on the graph is on heap, including the vector's data buffer and each element string's data buffer. With copy, str_b's data buffer has to be copied, which involves a buffer allocation then deallocation. With move, old str_b's data buffer is reused by the new str_b in the new address, no buffer allocation or deallocation is needed(As Howard pointed out, the "data" that old str_b now points to is unspecified). This brings a huge performance boost, yet it means more than that, because now you can store expensive objects into a vector without sacrificing performance, while previously having to store pointers. This also helps extend usage of value semantics.

Move Semantics and Resource Management

In the famous article Rule of Zero, the author wrote:

Using value semantics is essential for RAII, because references don’t affect the lifetime of their referrents.

I found it to be a good starting point to discuss the correlation between move semantics and resource management.

As you may or may not know, RAII has another name called Scope-Bound Resource Management (SBRM), after the basic use case where the lifetime of an RAII object ends due to scope exit. Remember one advantage of using value semantics? Safety. We know exactly when an object's lifetime starts and ends, just by looking at its storage duration, and 99% of the time we'll find it at block scope, which makes it very simple. Things get a lot more complicated for pointers and references, now we have to worry about whether the object that is referenced or pointed to has been released. This is hard, what makes it worse is that these objects usually exist in different scope from its pointers and references.

It's obvious why value semantics gets along well with RAII —— RAII binds the life cycle of a resource to the lifetime of an object, and with value semantics, you have a clear idea of an object's lifetime.

But, resource is about identity…

Though value semantics and RAII seems to be a perfect match, in reality it was not. Why? Fundamentally speaking, because resource is about identity, while value semantics only cares about value. You have an open socket, you use the very socket; you have an open file, you use the very file. In the context of resource management, there aren't things with the same value. A resource represents itself, with unique identity.

See the contradiction here? Prior to C++11, if we stick with value semantics, it was hard to work with resources cause they cannot be copied, therefore programmers came up with some workarounds:

  • Use raw pointers;
  • Write their own movable-but-not-copyable class(often Involves private copy ctor and operations like swap and splice);
  • Use auto_ptr.

These solutions intended to solve the problem of unique ownership and ownership transferring, but they all have some drawbacks. I won't talk about it here cause it's everywhere on the Internet. What I would like to address is that, even without move semantics, resource ownership management can be done, it's just that it takes more code and is often error-prone.

What is lacking is uniform syntax and semantics to enable generic code to move arbitrary objects (just as generic code today can copy arbitrary objects).

- Move Semantics Proposal

Compared to the above statement from proposal, I like this answer more:

In addition to the obvious efficiency benefit, this also affords a programmer a standards-compliant way to have objects that are movable but not copyable. Objects that are movable and not copyable convey a very clear boundary of resource ownership via standard language semantics …my point is that move semantics is now a standard way to concisely express (among other things) movable-but-not-copyable objects.

The above quote has done a pretty good job explaining what move semantics means to resource ownership management in C++. Resource should naturally be movable(by "movable" I mean transferrable) but not copyable, now with the help of move semantics(well actually a whole lot of change at language level to support it), there's a standard way to do this right and efficiently.

The Rebirth of Value Semantics

Finally, we are able to talk about the other aspect(besides performance) of augmentation that move semantics brought to value semantics.

Stepping through the above discussion, we've seen why value semantics fits the RAII model, but at the same time not compatible with resource management. With the arise of move semantics, the necessary materials to fill this gap is finally prepared. So here we have, smart pointers!

Needless to say the importance of std::unique_ptr and std::shared_ptr, here I'd like to emphasize three things:

  • They follow RAII;
  • They take huge advantage of move semantics(especially for unique_ptr);
  • They help keep value semantics.

For the third point, if you've read Rule of Zero, you know what I'm talking about. No need to use raw pointers to manage resources, EVER, just use unique_ptr directly or store as member variable, and you're done. When transferring resource ownership, the implicitly constructed move ctor is able to do the job well. Better yet, the current specification ensures that, a named value in the return statement in the worst case(i.e. without elisions) is treated as an rvalue. It means, returning by value should be the default choice for unique_ptr.

std::unique_ptr<ExpensiveResource> foo() {
  auto data = std::make_unique<ExpensiveResource>();
  return data;
}
std::unique_ptr<ExpensiveResource> p = foo();  // a move at worst

See here for a more detailed explanation. In fact, when using unique_ptr as function parameters, passing by value is still the best choice. I'll probably write an article about it, if time is available.

Besides smart pointers, std::string and std::vector are also RAII wrappers, and the resource they manage is heap memory. For these classes, return by value is still preferred. I'm not too sure about other things like std::thread or std::lock_guard cause I haven't got chance to use them.

To summarize, by utilizing smart pointers, value semantics now truly gains compatibility with RAII. At its core, this is powered by move semantics.

Summary

So far we've gone through a lot of concepts and you probably feel overwhelmed, but the points I want to convey are simple:

  1. Move semantics boosts performance while keeping value semantics;
  2. Move semantics helps bring every piece of resource management together to become what it is today. In particular, it is the key that makes value semantics and RAII truly work together, as it should have been long ago.

I'm a learner on this topic myself, so feel free to point out anything that you feel is wrong, I really appreciate it.

[1]: Here object means "a piece of memory that has an address, a type, and is capable of storing values", from Andrzej's C++ blog.

pdir2 现在支持 attribute filtering 了

最近应用户要求pdir2 添加了一个功能,在 0.3.0 中发布。

现在你可以更精确地获取所需属性了:
properties: Find properties/variables defined in the inspected object.

methods: Find methods/functions defined in the inspected object.

public: Find public attributes.

own: Find attributes that are not inherited from parent classes.

比如这个例子(完整文档参考 wiki

class Base(object):
    base_class_variable = 1

    def __init__(self):
        self.base_instance_variable = 2

    def base_method(self):
        pass


class DerivedClass(Base):
    derived_class_variable = 1

    def __init__(self):
        self.derived_instance_variable = 2
        super(DerivedClass, self).__init__()

    def derived_method(self):
        pass


inst = DerivedClass()

pdir(inst).properties  # 'base_class_variable', 'base_instance_variable',
                       # 'derived_class_variable', 'derived_instance_variable', '__class__',
                       # '__dict__', '__doc__', '__module__', '__weakref__'

pdir(inst).methods  # '__subclasshook__', '__delattr__', '__dir__', '__getattribute__',
                    # '__setattr__', '__init_subclass__', 'base_method',
                    # 'derived_method', '__format__', '__hash__', '__init__', '__new__',
                    # '__repr__', '__sizeof__', '__str__', '__reduce__', '__reduce_ex__',
                    # '__eq__', '__ge__', '__gt__', '__le__', '__lt__', '__ne__'

pdir(inst).public  # 'base_method', 'derived_method', 'base_class_variable',
                   # 'base_instance_variable', 'derived_class_variable',
                   # 'derived_instance_variable'

pdir(inst).own  # 'derived_method', '__init__', 'base_instance_variable',
                # 'derived_class_variable', 'derived_instance_variable', '__doc__',
                # '__module__'

pdir(inst).public.own.properties  # 'base_instance_variable','derived_class_variable',
                                  # 'derived_instance_variable'
pdir(inst).own.properties.public  # ditto
pdir(inst).properties.public.own  # ditto

pdir(inst).public.own.properties.search('derived_inst')  # 'derived_instance_variable'

支持 Method Chaining,支持和 search() 方法一起使用。大概就这样。


top