为什么c++ 0x中没有编译器生成的swap()方法?

时间:2022-09-06 12:48:19

C++ compilers automatically generate copy constructors and copy-assignment operators. Why not swap too?

c++编译器自动生成复制构造函数和复制分配操作符。为什么不换吗?

These days the preferred method for implementing the copy-assignment operator is the copy-and-swap idiom:

现在,实现copy-assignment操作符的首选方法是copy-and-swap习惯用法:

T& operator=(const T& other)
{
    T copy(other);
    swap(copy);
    return *this;
}

(ignoring the copy-elision-friendly form that uses pass-by-value).

(忽略使用按值传递的易于复制的表单)。

This idiom has the advantage of being transactional in the face of exceptions (assuming that the swap implementation does not throw). In contrast, the default compiler-generated copy-assignment operator recursively does copy-assignment on all base classes and data members, and that doesn't have the same exception-safety guarantees.

这种习惯用法的优点是在异常情况下(假设交换实现不抛出)是事务性的。相反,默认的编译器生成的复制分配操作符递归地在所有基类和数据成员上执行复制分配,并且没有相同的异常安全保证。

Meanwhile, implementing swap methods manually is tedious and error-prone:

同时,手工实现交换方法繁琐且容易出错:

  1. To ensure that swap does not throw, it must be implemented for all non-POD members in the class and in base classes, in their non-POD members, etc.
  2. 为了确保交换不抛出,必须在类和基类中,在非pod成员中实现所有非pod成员。
  3. If a maintainer adds a new data member to a class, the maintainer must remember to modify that class's swap method. Failing to do so can introduce subtle bugs. Also, since swap is an ordinary method, compilers (at least none I know of) don't emit warnings if the swap implementation is incomplete.
  4. 如果维护人员向类添加新数据成员,那么维护人员必须记住修改该类的交换方法。如果不这样做,可能会引入一些微妙的bug。而且,由于交换是一种普通的方法,如果交换实现不完整,编译器(至少我不知道)不会发出警告。

Wouldn't it be better if the compiler generated swap methods automatically? Then the implicit copy-assignment implementation could leverage it.

如果编译器自动生成交换方法不是更好吗?然后隐式复制分配实现可以利用它。

The obvious answer probably is: the copy-and-swap idiom didn't exist when C++ was developed, and doing this now might break existing code.

显而易见的答案可能是:在开发c++时,复制和交换习语并不存在,现在这样做可能会破坏现有的代码。

Still, maybe people could opt-in to letting the compiler generate swap using the same syntax that C++0x uses for controlling other implicit functions:

不过,也许人们可以选择加入,让编译器使用c++ 0x用于控制其他隐式函数的相同语法生成交换:

void swap() = default;

and then there could be rules:

然后还有规则:

  1. If there is a compiler-generated swap method, an implicit copy-assignment operator can be implemented using copy-and-swap.
  2. 如果有一个编译器生成的交换方法,那么可以使用copy-and-swap实现隐式的复制-分配操作符。
  3. If there is no compiler-generated swap method, an implicit copy-assignment operator would be implemented as before (invoking copy-assigment on all base classes and on all members).
  4. 如果没有编译器生成的交换方法,就会像以前一样实现隐式的复制分配操作符(在所有基类和所有成员上调用copy-assigment)。

Does anyone know if such (crazy?) things have been suggested to the C++ standards committee, and if so, what opinions committee members had?

有谁知道这些(疯狂的)事情是否已经向c++标准委员会提出了建议,如果是的话,委员会成员有什么意见?

4 个解决方案

#1


22  

This is in addition to Terry's answer.

这是特里的回答之外的。

The reason we had to make swap functions in C++ prior to 0x is because the general free-function std::swap was less efficient (and less versatile) than it could be. It made a copy of a parameter, then had two re-assignments, then released the essentially wasted copy. Making a copy of a heavy-weight class is a waste of time, when we as programmers know all we really need to do is swap the internal pointers and whatnot.

在0x之前,我们必须在c++中进行交换函数的原因是由于通用的free-function std::swap效率较低(且功能更少)。它复制了一个参数,然后进行了两次重赋,然后释放了本质上被浪费的副本。复制一个重量级的类是浪费时间,当我们作为程序员知道我们真正需要做的就是交换内部指针什么的时候。

However, rvalue-references relieve this completely. In C++0x, swap is implemented as:

然而,rvalue-references完全消除了这一点。在c++ 0x中,swap被实现为:

template <typename T>
void swap(T& x, T& y)
{
    T temp(std::move(x));
    x = std::move(y);
    y = std::move(temp);
}

This makes much more sense. Instead of copying data around, we are merely moving data around. This even allows non-copyable types, like streams, to be swapped. The draft of the C++0x standard states that in order for types to be swapped with std::swap, they must be rvalue constructable, and rvalue assignable (obviously).

这更有意义。我们只是在移动数据,而不是在周围复制数据。这甚至允许交换非可复制类型(如流)。c++ 0x标准的草案规定,为了让类型与std::swap交换,它们必须是rvalue构造表,而且rvalue是可赋值的(显然)。

This version of swap will essentially do what any custom written swap function would do. Consider a class we'd normally write swap for (such as this "dumb" vector):

这个版本的交换器将执行任何自定义编写的交换器功能。考虑一个我们通常会写交换的类(例如这个“哑”向量):

struct dumb_vector
{
    int* pi; // lots of allocated ints

    // constructors, copy-constructors, move-constructors
    // copy-assignment, move-assignment
};

Previously, swap would make a redundant copy of all our data, before discarding it later. Our custom swap function would just swap the pointer, but can be clumsy to use in some cases. In C++0x, moving achieves the same end result. Calling std::swap would generate:

在此之前,交换将对所有数据进行冗余复制,然后再丢弃它。我们的自定义交换函数只会交换指针,但是在某些情况下使用起来会很笨拙。在c++ 0x中,移动会得到相同的结果。调用std::swap会产生:

dumb_vector temp(std::move(x));
x = std::move(y);
y = std::move(temp);

Which translates to:

翻译为:

dumb_vector temp;
temp.pi = x.pi; x.pi = 0; // temp(std::move(x));
x.pi = y.pi; y.pi = 0; // x = std::move(y);
y.pi = temp.pi; temp.pi = 0; // y = std::move(temp);

The compiler will of course get rid of redundant assignment's, leaving:

编译器当然会去掉冗余赋值,留下:

int* temp = x.pi;
x.pi = y.pi;
y.pi = temp;

Which is exactly what our custom swap would have made in the first place. So while prior to C++0x I would agree with your suggestion, custom swap's aren't really necessary anymore, with the introduction of rvalue-references. std::swap will work perfectly in any class that implements move functions.

这正是我们的自定义交换首先要做的。因此,在c++ 0x之前,我同意您的建议,随着rvalue-references的引入,自定义交换不再是必要的了。交换将在任何实现移动函数的类中完美地工作。

In fact, I'd argue implementing a swap function should become bad practice. Any class that would need a swap function would also need rvalue functions. But in that case, there is simply no need for the clutter of a custom swap. Code size does increase (two ravlue functions versus one swap), but rvalue-references don't just apply for swapping, leaving us with a positive trade off. (Overall faster code, cleaner interface, slightly more code, no more swap ADL hassle.)

事实上,我认为实现交换函数应该成为一种不好的做法。任何需要交换函数的类也需要rvalue函数。但在这种情况下,根本不需要自定义交换的混乱。代码大小确实增加了(两个ravlue函数和一个交换),但是rvalue-references不仅仅适用于交换,这给我们带来了一个积极的权衡。

As for whether or not we can default rvalue functions, I don't know. I'll look it up later or maybe someone else can chime in, but that would sure be helpful. :)

至于我们是否可以默认rvalue函数,我不知道。我以后再查,或者其他人可以插话,但这肯定是有帮助的。:)

Even so, it makes sense to allow default rvalue functions instead of swap. So in essence, as long as they allow = default rvalue functions, your request has already been made. :)

即便如此,允许默认的rvalue函数而不是交换也是有意义的。因此,本质上,只要它们允许=默认的rvalue函数,您的请求就已经完成了。:)

EDIT: I did a bit of searching, and the proposal for = default move was proposal n2583. According to this (which I don't know how to read very well), it was "moved back." It is listed under the section titled "Not ready for C++0x, but open to resubmit in future ". So looks like it won't be part of C++0x, but may be added later.

编辑:我做了一些搜索,并且提议=默认移动是提议n2583。根据这个(我不知道怎么读得很好),它被“退回去”了。它列在标题为“c++ 0x还没有准备好,但是以后可以重新提交”的章节中。所以看起来它不会是c++ 0x的一部分,但是可能会在后面添加。

Somewhat disappointing. :(

有点让人失望。:(

EDIT 2: Looking around a bit more, I found this: Defining Move Special Member Functions which is much more recent, and does look like we can default move. Yay!

编辑2:环顾四周,我发现了这个:定义移动的特殊成员函数,这是最近的,看起来我们可以默认移动。耶!

#2


10  

swap, when used by STL algorithms, is a free function. There is a default swap implementation: std::swap. It does the obvious. You seem to be under the impression that if you add a swap member function to your data type, STL containers and algorithms will find it and use it. This isn't the case.

交换,当STL算法使用时,是一个*函数。有一个默认的交换实现:std: swap。它最明显的。您似乎认为,如果将交换成员函数添加到数据类型中,STL容器和算法就会找到并使用它。这并不是如此。

You're supposed to specialize std::swap (in the namespace next to your UDT, so it's found by ADL) if you can do better. It is idiomatic to just have it defer to a member swap function.

如果可以做得更好,您应该将std::swap专门化(在UDT旁边的名称空间中,ADL可以找到它)。让它服从于成员交换函数是惯用的。

While we're on the subject, it is also idiomatic in C++0x (in as much as is possible to have idioms on such a new standard) to implement rvalue constructors as a swap.

在我们讨论这个话题的同时,在c++ 0x中(尽可能多地使用这样一个新标准的习惯用法)实现rvalue构造函数作为交换。

And yes, in a world where a member swap was the language design instead of a free function swap, this would imply that we'd need a swap operator instead of a function - or else primitive types (int, float, etc) couldn't be treated generically (as they have no member function swap). So why didn't they do this? You'd have to ask the committee members for sure - but I'm 95% certain the reason is the committee has long preferred library implementions of features whenever possible, over inventing new syntax to implement a feature. The syntax of a swap operator would be weird, because unlike =, +, -, etc, and all the other operators, there is no algebraic operator everyone is familiar with for "swap".

是的,在这个世界上,一个成员交换的语言设计而不是一个免费的功能互换,这将意味着我们需要交换操作符,而不是一个函数——否则原始类型(整数、浮点数、等)无法处理一般(他们没有交换成员函数)。他们为什么不这么做呢?你必须向委员会成员保证——但我95%肯定的是,委员会长期以来一直偏爱图书馆的特性,而不是发明新语法来实现功能。交换运算符的语法会很奇怪,因为与=、+、-等和所有其他运算符不同,没有每个人都熟悉的代数运算符用于“交换”。

C++ is syntactically complex enough. They go to great lengths to not add new keywords or syntax features whenever possible, and only do so for very good reasons (lambdas!).

c++的语法非常复杂。只要有可能,他们就会不遗余力地不添加新的关键字或语法特性,而且只有非常好的理由(lambdas!)

#3


2  

Does anyone know if such (crazy?) things have been suggested to the C++ standards committee

有人知道这些(疯狂的)事情是否已经被推荐给c++标准委员会了吗

Send an email to Bjarne. He knows all of this stuff and usually replies within a couple of hours.

给Bjarne发邮件。他知道所有这些东西,通常会在几个小时内回复。

#4


1  

Is even compiler-generated move constructor/assignment planned (with the default keyword)?

甚至是编译器生成的move构造函数/赋值也在计划中(使用默认关键字)吗?

If there is a compiler-generated swap method, an implicit copy-assignment operator can be implemented using copy-and-swap.

如果有一个编译器生成的交换方法,那么可以使用copy-and-swap实现隐式的复制-分配操作符。

Even though the idiom leaves the object unchanged in case of exceptions, doesn't this idiom, by requiring the creation of a third object, make chances of failure greater in the first place?

即使这个习惯用法在出现异常时保持对象不变,但是这个习惯用法不是要求创建第三个对象,从而使失败的可能性更大吗?

There might also be performance implications (copying might be more expensive than "assigning") which is why I don't see such complicated functionality being left to be implemented by the compiler.

可能还有性能影响(复制可能比“分配”更昂贵),这就是为什么我没有看到编译器要实现的复杂功能。

Generally I don't overload operator= and I don't worry about this level of exception safety: I don't wrap individual assignments into try blocks - what would I do with the most likely std::bad_alloc at that point? - so I wouldn't care if the object, before they end up destroyed anyway, remained in the original state or not. There may be of course specific situations where you might indeed need it, but I don't see why the principle of "you don't pay for what you don't use" should be given up here.

一般来说,我不会重载操作符=,我也不担心这种级别的异常安全性:我不会将单个任务打包到try块中——那么,我该如何处理最可能的std: bad_alloc呢?所以我不在乎这个物体,在它们最终被摧毁之前,是否仍然保持原始状态。当然,在某些特定情况下,你可能确实需要它,但我不明白为什么“你不支付你不使用的东西”的原则应该被放弃。

#1


22  

This is in addition to Terry's answer.

这是特里的回答之外的。

The reason we had to make swap functions in C++ prior to 0x is because the general free-function std::swap was less efficient (and less versatile) than it could be. It made a copy of a parameter, then had two re-assignments, then released the essentially wasted copy. Making a copy of a heavy-weight class is a waste of time, when we as programmers know all we really need to do is swap the internal pointers and whatnot.

在0x之前,我们必须在c++中进行交换函数的原因是由于通用的free-function std::swap效率较低(且功能更少)。它复制了一个参数,然后进行了两次重赋,然后释放了本质上被浪费的副本。复制一个重量级的类是浪费时间,当我们作为程序员知道我们真正需要做的就是交换内部指针什么的时候。

However, rvalue-references relieve this completely. In C++0x, swap is implemented as:

然而,rvalue-references完全消除了这一点。在c++ 0x中,swap被实现为:

template <typename T>
void swap(T& x, T& y)
{
    T temp(std::move(x));
    x = std::move(y);
    y = std::move(temp);
}

This makes much more sense. Instead of copying data around, we are merely moving data around. This even allows non-copyable types, like streams, to be swapped. The draft of the C++0x standard states that in order for types to be swapped with std::swap, they must be rvalue constructable, and rvalue assignable (obviously).

这更有意义。我们只是在移动数据,而不是在周围复制数据。这甚至允许交换非可复制类型(如流)。c++ 0x标准的草案规定,为了让类型与std::swap交换,它们必须是rvalue构造表,而且rvalue是可赋值的(显然)。

This version of swap will essentially do what any custom written swap function would do. Consider a class we'd normally write swap for (such as this "dumb" vector):

这个版本的交换器将执行任何自定义编写的交换器功能。考虑一个我们通常会写交换的类(例如这个“哑”向量):

struct dumb_vector
{
    int* pi; // lots of allocated ints

    // constructors, copy-constructors, move-constructors
    // copy-assignment, move-assignment
};

Previously, swap would make a redundant copy of all our data, before discarding it later. Our custom swap function would just swap the pointer, but can be clumsy to use in some cases. In C++0x, moving achieves the same end result. Calling std::swap would generate:

在此之前,交换将对所有数据进行冗余复制,然后再丢弃它。我们的自定义交换函数只会交换指针,但是在某些情况下使用起来会很笨拙。在c++ 0x中,移动会得到相同的结果。调用std::swap会产生:

dumb_vector temp(std::move(x));
x = std::move(y);
y = std::move(temp);

Which translates to:

翻译为:

dumb_vector temp;
temp.pi = x.pi; x.pi = 0; // temp(std::move(x));
x.pi = y.pi; y.pi = 0; // x = std::move(y);
y.pi = temp.pi; temp.pi = 0; // y = std::move(temp);

The compiler will of course get rid of redundant assignment's, leaving:

编译器当然会去掉冗余赋值,留下:

int* temp = x.pi;
x.pi = y.pi;
y.pi = temp;

Which is exactly what our custom swap would have made in the first place. So while prior to C++0x I would agree with your suggestion, custom swap's aren't really necessary anymore, with the introduction of rvalue-references. std::swap will work perfectly in any class that implements move functions.

这正是我们的自定义交换首先要做的。因此,在c++ 0x之前,我同意您的建议,随着rvalue-references的引入,自定义交换不再是必要的了。交换将在任何实现移动函数的类中完美地工作。

In fact, I'd argue implementing a swap function should become bad practice. Any class that would need a swap function would also need rvalue functions. But in that case, there is simply no need for the clutter of a custom swap. Code size does increase (two ravlue functions versus one swap), but rvalue-references don't just apply for swapping, leaving us with a positive trade off. (Overall faster code, cleaner interface, slightly more code, no more swap ADL hassle.)

事实上,我认为实现交换函数应该成为一种不好的做法。任何需要交换函数的类也需要rvalue函数。但在这种情况下,根本不需要自定义交换的混乱。代码大小确实增加了(两个ravlue函数和一个交换),但是rvalue-references不仅仅适用于交换,这给我们带来了一个积极的权衡。

As for whether or not we can default rvalue functions, I don't know. I'll look it up later or maybe someone else can chime in, but that would sure be helpful. :)

至于我们是否可以默认rvalue函数,我不知道。我以后再查,或者其他人可以插话,但这肯定是有帮助的。:)

Even so, it makes sense to allow default rvalue functions instead of swap. So in essence, as long as they allow = default rvalue functions, your request has already been made. :)

即便如此,允许默认的rvalue函数而不是交换也是有意义的。因此,本质上,只要它们允许=默认的rvalue函数,您的请求就已经完成了。:)

EDIT: I did a bit of searching, and the proposal for = default move was proposal n2583. According to this (which I don't know how to read very well), it was "moved back." It is listed under the section titled "Not ready for C++0x, but open to resubmit in future ". So looks like it won't be part of C++0x, but may be added later.

编辑:我做了一些搜索,并且提议=默认移动是提议n2583。根据这个(我不知道怎么读得很好),它被“退回去”了。它列在标题为“c++ 0x还没有准备好,但是以后可以重新提交”的章节中。所以看起来它不会是c++ 0x的一部分,但是可能会在后面添加。

Somewhat disappointing. :(

有点让人失望。:(

EDIT 2: Looking around a bit more, I found this: Defining Move Special Member Functions which is much more recent, and does look like we can default move. Yay!

编辑2:环顾四周,我发现了这个:定义移动的特殊成员函数,这是最近的,看起来我们可以默认移动。耶!

#2


10  

swap, when used by STL algorithms, is a free function. There is a default swap implementation: std::swap. It does the obvious. You seem to be under the impression that if you add a swap member function to your data type, STL containers and algorithms will find it and use it. This isn't the case.

交换,当STL算法使用时,是一个*函数。有一个默认的交换实现:std: swap。它最明显的。您似乎认为,如果将交换成员函数添加到数据类型中,STL容器和算法就会找到并使用它。这并不是如此。

You're supposed to specialize std::swap (in the namespace next to your UDT, so it's found by ADL) if you can do better. It is idiomatic to just have it defer to a member swap function.

如果可以做得更好,您应该将std::swap专门化(在UDT旁边的名称空间中,ADL可以找到它)。让它服从于成员交换函数是惯用的。

While we're on the subject, it is also idiomatic in C++0x (in as much as is possible to have idioms on such a new standard) to implement rvalue constructors as a swap.

在我们讨论这个话题的同时,在c++ 0x中(尽可能多地使用这样一个新标准的习惯用法)实现rvalue构造函数作为交换。

And yes, in a world where a member swap was the language design instead of a free function swap, this would imply that we'd need a swap operator instead of a function - or else primitive types (int, float, etc) couldn't be treated generically (as they have no member function swap). So why didn't they do this? You'd have to ask the committee members for sure - but I'm 95% certain the reason is the committee has long preferred library implementions of features whenever possible, over inventing new syntax to implement a feature. The syntax of a swap operator would be weird, because unlike =, +, -, etc, and all the other operators, there is no algebraic operator everyone is familiar with for "swap".

是的,在这个世界上,一个成员交换的语言设计而不是一个免费的功能互换,这将意味着我们需要交换操作符,而不是一个函数——否则原始类型(整数、浮点数、等)无法处理一般(他们没有交换成员函数)。他们为什么不这么做呢?你必须向委员会成员保证——但我95%肯定的是,委员会长期以来一直偏爱图书馆的特性,而不是发明新语法来实现功能。交换运算符的语法会很奇怪,因为与=、+、-等和所有其他运算符不同,没有每个人都熟悉的代数运算符用于“交换”。

C++ is syntactically complex enough. They go to great lengths to not add new keywords or syntax features whenever possible, and only do so for very good reasons (lambdas!).

c++的语法非常复杂。只要有可能,他们就会不遗余力地不添加新的关键字或语法特性,而且只有非常好的理由(lambdas!)

#3


2  

Does anyone know if such (crazy?) things have been suggested to the C++ standards committee

有人知道这些(疯狂的)事情是否已经被推荐给c++标准委员会了吗

Send an email to Bjarne. He knows all of this stuff and usually replies within a couple of hours.

给Bjarne发邮件。他知道所有这些东西,通常会在几个小时内回复。

#4


1  

Is even compiler-generated move constructor/assignment planned (with the default keyword)?

甚至是编译器生成的move构造函数/赋值也在计划中(使用默认关键字)吗?

If there is a compiler-generated swap method, an implicit copy-assignment operator can be implemented using copy-and-swap.

如果有一个编译器生成的交换方法,那么可以使用copy-and-swap实现隐式的复制-分配操作符。

Even though the idiom leaves the object unchanged in case of exceptions, doesn't this idiom, by requiring the creation of a third object, make chances of failure greater in the first place?

即使这个习惯用法在出现异常时保持对象不变,但是这个习惯用法不是要求创建第三个对象,从而使失败的可能性更大吗?

There might also be performance implications (copying might be more expensive than "assigning") which is why I don't see such complicated functionality being left to be implemented by the compiler.

可能还有性能影响(复制可能比“分配”更昂贵),这就是为什么我没有看到编译器要实现的复杂功能。

Generally I don't overload operator= and I don't worry about this level of exception safety: I don't wrap individual assignments into try blocks - what would I do with the most likely std::bad_alloc at that point? - so I wouldn't care if the object, before they end up destroyed anyway, remained in the original state or not. There may be of course specific situations where you might indeed need it, but I don't see why the principle of "you don't pay for what you don't use" should be given up here.

一般来说,我不会重载操作符=,我也不担心这种级别的异常安全性:我不会将单个任务打包到try块中——那么,我该如何处理最可能的std: bad_alloc呢?所以我不在乎这个物体,在它们最终被摧毁之前,是否仍然保持原始状态。当然,在某些特定情况下,你可能确实需要它,但我不明白为什么“你不支付你不使用的东西”的原则应该被放弃。