【问题标题】:Why does STL's std::sort not work with immutable classes?为什么 STL 的 std::sort 不适用于不可变类?
【发布时间】:2014-04-08 15:06:08
【问题描述】:

Std::sort 在类属性可变时起作用。例如,以下代码有效,并且向量按预期按升序排序。

class PersonMutable
{
public:

    PersonMutable(int age, std::string name):
        Age(age),Name(name)
    {
    }



    int Age;
    std::string Name;
};



void TestSort()
{
    std::vector<PersonMutable> people;
    people.push_back(PersonMutable(24,"Kerry"));
    people.push_back(PersonMutable(30,"Brian"));
    people.push_back(PersonMutable(3,"James"));
    people.push_back(PersonMutable(28,"Paul"));

    std::sort(people.begin(),people.end(),
        [](const PersonMutable& a, PersonMutable & b) -> bool
    {
        return a.Age < b.Age;
    });
}

但是当同一个类不可变时,它与 std::sort 不兼容。

class PersonImmutable
{
public:

    PersonImmutable(int age, std::string name):
        Age(age),Name(name)
    {
    }

    PersonImmutable& operator=(const PersonImmutable& a)
    {
        PersonImmutable b(a.Age,a.Name);
        return b;
    }

    const int Age;
    const std::string Name;
};


void TestSort()
{
    std::vector<PersonImmutable> people;
    people.push_back(PersonImmutable(24,"Kerry"));
    people.push_back(PersonImmutable(30,"Brian"));
    people.push_back(PersonImmutable(3,"James"));
    people.push_back(PersonImmutable(28,"Paul"));

    std::sort(people.begin(),people.end(),
        [](const PersonImmutable& a, PersonImmutable & b) -> bool
    {
        return a.Age < b.Age;
    });
}

谁能告诉我为什么?

非常感谢。

【问题讨论】:

  • 你能解释一下你如何期望std::sort 在不改变序列的情况下交换元素吗?
  • 另外,请注意您的 operator= 通过引用返回堆栈上的值,这将导致未定义的行为...
  • @Agnew 从概念上讲,对序列中的元素进行排序不应该改变items,而是改变sequence 本身。 (例如:如果我手动按花色对扑克牌进行排序,我不会修改单张牌的属性;只修改它们在牌组中的位置。)问题中指出的混淆源于 std::sort 实际上的 C++ 机制运行。
  • @Lilshieste:如果您对指针进行排序,卡片类比会更好 - 有效地跟踪哪些卡片在哪里而不接触卡片,但是当您考虑到矢量内容是内存位置并且它可能很多时使用赋值覆盖元素比使用破坏/复制构造(更好的是 - 交换)更有效,实际行为变得直观而可取。
  • 具有const 成员的课程比他们的价值更麻烦,IMO。改为封装。

标签: c++ sorting c++11 stl


【解决方案1】:

C++ 的std::sort 要求被排序的迭代器实现ValueSwappable

类型 T 是 ValueSwappable 如果

  1. T 型满足迭代器要求
  2. 对于任何 T 类型的可解引用对象 x(即除结束迭代器之外的任何值),*x 满足 Swappable 要求。

并且要可交换,您基本上需要这样才能工作:

using std::swap;
swap(*x, *y);

此外,std::sort 要求以下表达式有效(MoveConstructible MoveAssignable

定义:

  • tT 类型的可修改左值。
  • rvT 类型的右值表达式。

要求:

  1. t = rv;
  2. T u = rv;
  3. T(rv);

您的编译器似乎已损坏...

您提供的代码确实满足这些要求。所以我不确定你的编译器为什么拒绝这段代码。由于operator= 过载,您的PersonImmutable 确实实现了std::swap 的要求。

你的不可变对象不应该满足这个要求(因为它是不可变的)......

话虽如此,您的operator= 重载将导致编译器崩溃,因为您通过引用返回了一个堆栈变量。

operator= 重载几乎总是通过引用返回 *this。这需要改变对象。所以它在不可变对象中没有多大意义。

您真的需要对这些对象进行排序吗?

如果您必须对它们进行排序,有一些选项。

  1. 您可以对指针向量进行排序。
  2. 您可以对不可变对象的 std::list 进行排序。
  3. 还有其他选择..

此代码的最小(ish)测试用例...

一个有效的编译器应该接受以下代码为有效的。听起来你的没有。

#include <string>

class PersonImmutable {
    public:
        PersonImmutable(int age): Age(age) {}

        PersonImmutable operator=(const PersonImmutable& a) {
            return *this;
        }

    private:
        const int Age;
};

int main() {
    PersonImmutable a(1, "a");
    PersonImmutable b(2, "b");

    using std::swap;
    swap(a,b);
}

【讨论】:

  • 您缺少值类型的 MoveConstructible 和 MoveAssignable 要求。
  • @DieterLücking:实际上只有在使用 std::swap 重载时才需要。如果您使用自定义交换,则实际上不需要。
  • 它不能编译的原因(至少对我来说是在 gcc 上)似乎是因为 lambda 的第二个参数是对非 const 的引用,而一些内部辅助函数使参数成为 const ref .根据cppreference 的说法,比较器的签名不一定必须采用 const-refs,所以也许它确实是标准库中的一个错误。但是OP中的代码无论如何都被破坏了,所以......不知道。
  • @sharth 再看看 25.4.1.1 排序
  • @user2822838 PersonImmutable a(1); PersonImmutable b(2); a = b; // a 发生了变异
猜你喜欢
  • 2020-11-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-09
  • 1970-01-01
  • 2019-10-03
  • 2014-12-29
  • 2015-04-21
相关资源
最近更新 更多