使交换更快,更易于使用和异常安全 [英] Making swap faster, easier to use and exception-safe

查看:140
本文介绍了使交换更快,更易于使用和异常安全的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我昨晚睡不着,开始思考 std :: swap 。这是熟悉的C ++ 98版本:

I could not sleep last night and started thinking about std::swap. Here is the familiar C++98 version:

template <typename T>
void swap(T& a, T& b)
{
    T c(a);
    a = b;
    b = c;
}

如果用户定义的类 Foo 使用外部资源,这是低效的。常见的成语是提供一个方法 void Foo :: swap(Foo& other) std :: swap< Foo> code>。请注意,这不适用于类模板,因为您不能部分专门化功能模板,并且重载 std 命名空间中的名称是非法的。解决方案是在自己的命名空间中写一个模板函数,并依赖于依赖于参数的查找来找到它。这取决于客户端遵循使用std :: swap idiom而不是调用 std :: swap 直。非常脆。

If a user-defined class Foo uses external ressources, this is inefficient. The common idiom is to provide a method void Foo::swap(Foo& other) and a specialization of std::swap<Foo>. Note that this does not work with class templates since you cannot partially specialize a function template, and overloading names in the std namespace is illegal. The solution is to write a template function in one's own namespace and rely on argument dependent lookup to find it. This depends critically on the client to follow the "using std::swap idiom" instead of calling std::swap directly. Very brittle.

在C ++ 0x中,如果 Foo 有一个用户定义的移动构造函数和移动赋值运算符,提供自定义交换方法和 std :: swap< Foo> 因为 std :: swap 的C ++ 0x版本使用有效的移动而不是副本:

In C++0x, if Foo has a user-defined move constructor and a move assignment operator, providing a custom swap method and a std::swap<Foo> specialization has little to no performance benefit, because the C++0x version of std::swap uses efficient moves instead of copies:

#include <utility>

template <typename T>
void swap(T& a, T& b)
{
    T c(std::move(a));
    a = std::move(b);
    b = std::move(c);
}

不必干扰交换已经耗费了很多的负担从程序员。
当前编译器不会自动生成移动构造函数和移动赋值运算符,但据我所知,这将改变。唯一的问题是异常安全,因为一般来说,移动操作是允许抛出的,这打开了一整个蠕虫。问题移动对象的状态究竟是什么?

Not having to fiddle with swap anymore already takes a lot of burden away from the programmer. Current compilers do not generate move constructors and move assignment operators automatically yet, but as far as I know, this will change. The only problem left then is exception-safety, because in general, move operations are allowed to throw, and this opens up a whole can of worms. The question "What exactly is the state of a moved-from object?" complicates things further.

然后我在想,C ++中 std :: swap 的语义是什么? + 0x如果一切顺利?交换之前和之后的对象的状态是什么?通常,通过移动操作交换不会触及外部资源,只有平面对象表示自身。

Then I was thinking, what exactly are the semantics of std::swap in C++0x if everything goes fine? What is the state of the objects before and after the swap? Typically, swapping via move operations does not touch external resources, only the "flat" object representations themselves.

因此,为什么不写简单的 swap 模板:交换对象表示

So why not simply write a swap template that does exactly that: swap the object representations?

#include <cstring>

template <typename T>
void swap(T& a, T& b)
{
    unsigned char c[sizeof(T)];

    memcpy( c, &a, sizeof(T));
    memcpy(&a, &b, sizeof(T));
    memcpy(&b,  c, sizeof(T));
}

这是一样高效的:它只是通过原始内存。它不需要用户的任何干预:不必定义特殊的交换方法或移动操作。这意味着它甚至可以在C ++ 98(它没有右值引用,注意你)。但更重要的是,我们现在可以忘记异常安全问题,因为 memcpy 从不会引发。

This is as efficient as it gets: it simply blasts through raw memory. It does not require any intervention from the user: no special swap methods or move operations have to be defined. This means that it even works in C++98 (which does not have rvalue references, mind you). But even more importantly, we can now forget about the exception-safety issues, because memcpy never throws.

我可以看到这种方法的两个潜在问题:

I can see two potential problems with this approach:

首先,并不是所有对象都被交换。如果类设计器隐藏了复制构造函数或复制赋值操作符,那么试图交换该类的对象应该在编译时失败。我们可以简单地引入一些死代码,检查复制和赋值是否合法的类型:

First, not all objects are meant to be swapped. If a class designer hides the copy constructor or the copy assignment operator, trying to swap objects of the class should fail at compile-time. We can simply introduce some dead code that checks whether copying and assignment are legal on the type:

template <typename T>
void swap(T& a, T& b)
{
    if (false)    // dead code, never executed
    {
        T c(a);   // copy-constructible?
        a = b;    // assignable?
    }

    unsigned char c[sizeof(T)];

    std::memcpy( c, &a, sizeof(T));
    std::memcpy(&a, &b, sizeof(T));
    std::memcpy(&b,  c, sizeof(T));
}

任何正式的编译器都可以轻易地摆脱死代码。 (可能有更好的方法来检查交换一致性,但这不是重点,重要的是可能)。

Any decent compiler can trivially get rid of the dead code. (There are probably better ways to check the "swap conformance", but that is not the point. What matters is that it's possible).

其次,一些类型可能执行异常操作在复制构造函数和复制赋值运算符。例如,他们可能通知观察员他们的变化。我认为这是一个小问题,因为这样的对象可能不应该提供复制操作的第一位。

Second, some types might perform "unusual" actions in the copy constructor and copy assignment operator. For example, they might notify observers of their change. I deem this a minor issue, because such kinds of objects probably should not have provided copy operations in the first place.

请让我知道你的想法这种方法交换。它会在实践中工作吗?你会用吗?你能识别出这会破坏的库类型吗?您是否看到其他问题?讨论!

Please let me know what you think of this approach to swapping. Would it work in practice? Would you use it? Can you identify library types where this would break? Do you see additional problems? Discuss!

推荐答案


所以为什么不写一个 swap 模板,这样做就是:交换对象表示*?

So why not simply write a swap template that does exactly that: swap the object representations*?

一个对象,一旦被构造,可以破坏,当你复制它驻留的字节。事实上,一个可能出现一个看似无尽的情况下 这将不会做正确的事情 - 尽管在实践中它可能在98%的所有情况下工作。

There's many ways in which an object, once being constructed, can break when you copy the bytes it resides in. In fact, one could come up with a seemingly endless number of cases where this would not do the right thing - even though in practice it might work in 98% of all cases.

这是因为所有这一切的基本问题是,除了在C中,在C ++中,我们 不能把对象看成是纯粹的字节。这就是为什么我们有建设和破坏,毕竟:将原始存储变成对象和对象回到原始存储。一旦构造函数运行,对象所在的内存不仅仅是原始存储。如果你把它当作不是,你会打破一些类型。

That's because the underlying problem to all this is that, other than in C, in C++ we must not treat objects as if they are mere raw bytes. That's why we have construction and destruction, after all: to turn raw storage into objects and objects back into raw storage. Once a constructor has run, the memory where the object resides is more than only raw storage. If you treat it as if it weren't, you will break some types.

但是,移动对象本质上不应该比你的想法差得多,因为一旦开始递归调用 std :: move(),通常最终到达内置函数 的移动位置。 (如果有更多的移动为某些类型,你最好不要搞砸那些自己的记忆)。批准,移动内存在集合通常比单个移动(而且编译器不太可能发现它可以优化单个移动到一个包括 std :: memcpy()),但这是我们为抽象不透明对象提供给我们的价格。它相当小,特别是当你比较它和我们以前做的复制。

However, essentially, moving objects shouldn't perform that much worse than your idea, because, once you start to recursively inline the calls to std::move(), you usually ultimately arrive at where built-ins are moved. (And if there's more to moving for some types, you'd better not fiddle with the memory of those yourself!) Granted, moving memory en bloc is usually faster than single moves (and it's unlikely that a compiler might find out that it could optimize the individual moves to one all-encompassing std::memcpy()), but that's the price we pay for the abstraction opaque objects offer us. And it's quite small, especially when you compare it to the copying we used to do.

但您可以使用 std :: memcpy来优化 swap() ()替换 聚合类型

You could, however, have an optimized swap() using std::memcpy() for aggregate types.

这篇关于使交换更快,更易于使用和异常安全的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆