代码之家  ›  专栏  ›  技术社区  ›  jamesdlin

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

  •  44
  • jamesdlin  · 技术社区  · 16 年前

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

    现在,实现复制分配运算符的首选方法是复制和交换惯用法:

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

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

    这个成语的优点是在遇到异常时具有事务性(假定 掉期 实现不会抛出)。相反,默认的编译器生成的复制分配运算符递归地对所有的基类和数据成员执行复制分配,并且不具有相同的异常安全保证。

    同时,实施 互换 手动方法繁琐且容易出错:

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

    如果编译器生成 掉期 方法是否自动?然后,隐式副本分配实现可以利用它。

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

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

    void swap() = default;
    

    然后可能会有规则:

    1. 如果有编译器生成 掉期 方法,可以使用copy和swap实现隐式复制分配运算符。
    2. 如果没有生成编译器 掉期 方法,将像以前一样实现隐式复制分配运算符(在所有基类和所有成员上调用复制分配)。

    有人知道(疯了吗?)已经向C++标准委员会提出了一些建议,如果有的话,委员会的意见是什么?

    4 回复  |  直到 7 年前
        1
  •  23
  •   GManNickG    16 年前

    这是特里回答的补充。

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

    然而,右值引用完全消除了这一点。在C++ 0x中, 互换 执行为:

    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::掉期 它们必须是可构造的右值和可分配的右值(显然)。

    本版本 掉期 基本上会像任何自定义的写入交换函数那样做。考虑一个我们通常会写的类 掉期 对于(例如这个“哑”向量):

    struct dumb_vector
    {
        int* pi; // lots of allocated ints
    
        // constructors, copy-constructors, move-constructors
        // copy-assignment, move-assignment
    };
    

    以前, 掉期 将对所有数据进行冗余复制,然后丢弃。我们的习俗 掉期 函数只是交换指针,但在某些情况下使用起来可能很笨拙。在C++0x中,移动实现相同的结果。打电话 STD::掉期 将产生:

    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;
    

    哪个是 确切地 我们的习俗 掉期 一开始就应该做的。因此,在C++之前,我会同意你的建议,习惯 掉期 通过引入右值引用,S不再是真正必要的了。 STD::掉期 在任何实现move函数的类中都能很好地工作。

    事实上,我认为实施 掉期 功能应该成为不好的实践。任何需要 掉期 函数还需要右值函数。但是在这种情况下,根本就不需要一个杂乱无章的习惯 掉期 .代码大小会增加(两个ravlue函数与一个相比 掉期 但右值引用不仅适用于交换,还使我们获得了积极的权衡。(总的来说,代码更快,接口更干净,代码略多,不再多 掉期 ADL麻烦。

    至于我们是否可以 default 右值函数,我不知道。我稍后再查,或许有人能插话,但那肯定会有帮助。:)

    即使如此,允许 违约 右值函数而不是 掉期 . 所以在本质上,只要他们允许 = default 值函数,您的请求已经发出。:)

    编辑:我做了一点搜索,和建议 =默认值 这是提议 n2583 . 根据 this (我不知道如何读得很好),它被“搬回去了”,它被列在标题“不准备为C++ 0x,但打开以后重新提交”的章节下。所以看起来它不是C++0X的一部分,但以后可以添加。

    有点令人失望。:(

    编辑2:再多看看,我发现: Defining Move Special Member Functions 这是最近的,看起来我们可以违约 move . 哎呀!

        2
  •  11
  •   Nikolai Fetissov    16 年前

    当stl算法使用swap时,它是一个自由函数。 . 有一个默认的交换实现: std::swap . 这很明显。您似乎觉得,如果向数据类型添加交换成员函数,STL容器和算法将找到并使用它。事实并非如此。

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

    当我们在这个主题上时,C++中的习惯用法(尽可能地在这样的新标准上有成语)也可以将R值构造函数作为交换来实现。

    是的,在一个成员交换是语言设计而不是自由函数交换的世界中,这意味着我们需要一个交换运算符而不是函数,否则就不能对原始类型(in t、float等)进行一般性的处理(因为它们没有成员函数交换)。他们为什么不这么做?你必须向委员会成员保证——但我有95%的把握,原因是委员会一直以来都倾向于尽可能地实现特性库,而不是发明新的语法来实现特性。交换运算符的语法会很奇怪,因为与=、+、-等以及所有其他运算符不同,没有任何代数运算符人人都熟悉“交换”。

    C++在语法上是足够复杂的。他们竭尽全力不添加任何可能的新关键字或语法特性,并且这样做只是出于非常好的理由(lambdas!).

        3
  •  2
  •   fredoverflow    16 年前

    有人知道(疯了吗?)已经向C++标准委员会提出了建议。

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

        4
  •  1
  •   UncleBens    16 年前

    甚至编译器生成的移动构造函数/分配是否计划(使用 违约 关键字?

    如果存在编译器生成的交换 方法,隐式副本分配 操作员可以使用 复制和交换。

    即使成语在例外情况下使对象保持不变,但这个成语是否要求创建第三个对象,从而使失败的机会在第一时间更大?

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

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

    推荐文章