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

发布于 2024-08-18 06:32:29 字数 1268 浏览 4 评论 0原文

C++ 编译器自动生成复制构造函数和复制赋值运算符。为什么不也交换呢?

如今,实现复制赋值运算符的首选方法是复制和交换习惯用法:

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

(忽略使用按值传递的复制省略友好形式)。

这种习惯用法的优点是在遇到异常时可以进行事务处理(假设 swap 实现不会抛出异常)。相反,编译器生成的默认复制赋值运算符会递归地对所有基类和数据成员进行复制赋值,并且不具有相同的异常安全保证。

同时,手动实现 swap 方法既繁琐又容易出错:

  1. 为了确保 swap 不会抛出异常,必须为类中和 in 中的所有非 POD 成员实现它。基类、非 POD 成员等。
  2. 如果维护者向类添加新的数据成员,则维护者必须记住修改该类的 swap 方法。如果不这样做可能会引入微妙的错误。此外,由于 swap 是一种普通方法,因此如果 swap 实现不完整,编译器(至少我知道没有)不会发出警告。

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

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

尽管如此,也许人们可以选择让编译器使用 C++0x 用于控制其他隐式函数的相同语法生成 swap

void swap() = default;

然后可能有规则:

  1. 如果有一个编译器 -生成的 swap 方法,可以使用复制和交换来实现隐式复制赋值运算符。
  2. 如果没有编译器生成的 swap 方法,则将像以前一样实现隐式复制分配运算符(对所有基类和所有成员调用复制分配)。

有谁知道这样的(疯狂的?)事情是否已被建议给 C++ 标准委员会,如果是的话,委员会成员有什么意见?

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

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

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

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.

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:

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. 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).

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

离笑几人歌 2024-08-25 06:32:29

这是对特里答案的补充。

我们必须在 0x 之前在 C++ 中创建 swap 函数的原因是,一般的自由函数 std::swap 效率较低(且通用性较差)。 。它制作了一个参数的副本,然后进行了两次重新分配,然后释放了本质上浪费的副本。制作重量级类的副本是浪费时间,因为我们作为程序员知道我们真正需要做的就是交换内部指针等等。

然而,右值引用完全缓解了这个问题。在 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);
}

这更有意义。我们只是移动数据,而不是复制数据。这甚至允许交换不可复制的类型,例如流。 C++0x 标准草案规定,为了使用 std::swap 交换类型,它们必须是右值可构造的,并且右值可分配的(显然)。

此版本的 swap 本质上将执行任何自定义编写的交换函数将执行的操作。考虑一个我们通常为其编写 swap 的类(例如这个“哑”向量):

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

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

以前,swap 会在丢弃之前对所有数据进行冗余副本之后。我们的自定义 swap 函数只会交换指针,但在某些情况下使用起来可能很笨拙。在 C++0x 中,移动实现相同的最终结果。调用 std::swap 会生成:

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

翻译为:

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

编译器当然会删除多余的赋值,留下:

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

这正是我们自定义的 swap< /code> 首先会做。因此,虽然在 C++0x 之前我同意您的建议,但随着右值引用的引入,自定义交换实际上不再是必要的了。 std::swap 将在任何实现移动函数的类中完美工作。

事实上,我认为实现 swap 函数应该成为不好的做法。任何需要交换函数的类也需要右值函数。但在这种情况下,根本不需要混乱的自定义交换。代码大小确实增加了(两个 ravlue 函数与一个 swap),但右值引用不仅仅适用于交换,这给我们带来了积极的权衡。 (总体上代码更快,界面更干净,代码稍微多一点,没有更多交换 ADL麻烦。)

至于我们是否可以默认右值函数,我不知道。我稍后会查一下,或者也许其他人可以插话,但这肯定会有帮助。 :)

即便如此,允许 default 右值函数而不是 swap 也是有意义的。所以本质上,只要它们允许 = default 右值函数,你的请求就已经被提出了。 :)

编辑:我做了一些搜索,= default 移动的提案是提案 n2583。根据 this (我不不知道怎么读),就被“搬回来了”。它列在标题为“尚未准备好用于 C++0x,但开放以在将来重新提交”的部分下。所以看起来它不会成为 C++0x 的一部分,但可能会在以后添加。

有点令人失望。 :(

编辑2:再环顾一下,我发现了这个: 定义移动特殊成员函数这是最近的事情,看起来我们确实可以默认移动 耶!

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.

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

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).

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:

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.

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.)

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. :)

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. :)

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.

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!

百合的盛世恋 2024-08-25 06:32:29

当 STL 算法使用交换时,它是一个免费函数。有一个默认的交换实现:std::swap。它的作用显而易见。您似乎有这样的印象:如果您向数据类型添加交换成员函数,STL 容器和算法就会找到它并使用它。事实并非如此。

如果你能做得更好,你应该专门化 std::swap (在你的 UDT 旁边的命名空间中,所以它是由 ADL 找到的)。惯用做法是让它遵循成员交换函数。

当我们讨论这个主题时,在 C++0x 中将右值构造函数实现为交换也是惯用的(尽可能多地在这样的新标准上使用惯用语)。

是的,在一个成员交换是语言设计而不是自由函数交换的世界中,这意味着我们需要一个交换运算符而不是函数 - 否则原始类型(int、float 等)就不能'不能被一般对待(因为它们没有成员函数交换)。那么他们为什么不这样做呢?你必须询问委员会成员来确定 - 但我 95% 确定原因是委员会长期以来一直首选库实现功能,而不是发明新语法来实现功能。交换运算符的语法会很奇怪,因为与 =、+、- 等以及所有其他运算符不同,“交换”没有每个人都熟悉的代数运算符。

C++ 在语法上足够复杂。他们不遗余力地尽可能不添加新的关键字或语法功能,并且只有出于非常充分的理由才这样做(lambda!)。

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.

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.

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.

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".

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!).

难以启齿的温柔 2024-08-25 06:32:29

有谁知道是否有人向 C++ 标准委员会建议过这样的(疯狂的?)事情

发送电子邮件给 Bjarne。他知道所有这些事情,通常会在几个小时内回复。

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

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

在梵高的星空下 2024-08-25 06:32:29

甚至编译器生成的移动构造函数/赋值是否已计划(使用 default 关键字)?

如果有编译器生成的交换
方法,隐式复制赋值
运算符可以使用来实现
复制和交换。

即使该习惯用法在发生异常时保持对象不变,但该习惯用法要求创建第三个对象,这是否首先会增加失败的可能性?

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

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

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

If there is a compiler-generated swap
method, an implicit copy-assignment
operator can be implemented using
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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文