c++ - 使用右值的比较函数

标签 c++ tuples comparison rvalue-reference

这里尝试为 Foo 类创建自定义比较器.它将对成员应用一些转换,然后按字典顺序比较它们:

struct Foo {
    std::string s;
    float x;
    std::vector<int> z;
    std::unique_ptr<std::deque<double>> p;

    friend bool operator<(const Foo& lhs, const Foo& rhs) {
        auto make_comparison_object = [](const Foo& foo) {
            return std::forward_as_tuple(
                foo.s,
                -foo.x,
                std::accumulate(
                    foo.z.begin(),
                    foo.z.end(),
                    0),
                foo.p ? std::make_optional(*foo.p) : std::nullopt);
        };
        return make_comparison_object(lhs) < make_comparison_object(rhs);
    }
};

虽然优雅,但这里有一个问题:右值引用,例如对 -foo.x 结果的引用, 没有充分延长它们指向的右值的生命周期;它们将在 lambda 结束时被销毁。因此 return make_comparison_object(lhs) < make_comparison_object(rhs);将访问悬挂引用并导致未定义的行为。

我可以看到两种解决方法:

  • 使用 std::make_tuple而不是 std::forward_as_tuple .这会起作用,但我担心它可能会产生额外的拷贝或移动,特别是我认为它可能会复制传递给 std::make_tuple 的任何左值。例如foo.s .

  • 内联 lambda 的内容,如下所示:

    return std::forward_as_tuple(
            lhs.s,
            -lhs.x,
            std::accumulate(
                lhs.z.begin(),
                lhs.z.end(),
                0),
            lhs.p ? std::make_optional(*lhs.p) : std::nullopt)
        < std::forward_as_tuple(
            rhs.s,
            -rhs.x,
            std::accumulate(
                rhs.z.begin(),
                rhs.z.end(),
                0),
            rhs.p ? std::make_optional(*rhs.p) : std::nullopt);
    

这也行,但它看起来很糟糕并且违反了 DRY。

有没有更好的方法来完成这种比较?

编辑:这是一些比较建议解决方案的测试代码:

#include <functional>
#include <iostream>
#include <tuple>

#define BEHAVIOR 2

struct A {
    A(int data) : data(data) { std::cout << "constructor\n"; }
    A(const A& other) : data(other.data) { std::cout << "copy constructor\n"; }
    A(A&& other) : data(other.data) { std::cout << "move constructor\n"; }

    friend bool operator<(const A& lhs, const A& rhs) {
        return lhs.data < rhs.data;
    }

    int data;
};

A f(const A& a) {
    return A{-a.data};
}

struct Foo {
    Foo(A a1, A a2) : a1(std::move(a1)), a2(std::move(a2)) {}

    A a1;
    A a2;

    friend bool operator<(const Foo& lhs, const Foo& rhs) {
        #if BEHAVIOR == 0
        auto make_comparison_object = [](const Foo& foo) {
            return std::make_tuple(foo.a1, f(foo.a2));
        };
        return make_comparison_object(lhs) < make_comparison_object(rhs);
        #elif BEHAVIOR == 1
        auto make_comparison_object = [](const Foo& foo) {
            return std::make_tuple(std::ref(foo.a1), f(foo.a2));
        };
        return make_comparison_object(lhs) < make_comparison_object(rhs);
        #elif BEHAVIOR == 2
        return std::forward_as_tuple(lhs.a1, f(lhs.a2))
             < std::forward_as_tuple(rhs.a1, f(rhs.a2));
        #endif
    }
};

int main() {
    Foo foo1(A{2}, A{3});
    Foo foo2(A{2}, A{1});
    std::cout << "===== comparison start =====\n";
    auto result = foo1 < foo2;
    std::cout << "===== comparison end, result: " << result << " =====\n";
}

您可以在 Wandbox 上试用.结果在 gcc/clang 上是一致的,并且考虑到元组构造中的内容是有意义的:

  • std::make_tuple : 2份,2次移动
  • std::make_tuplestd::ref : 0 份,2 次移动
  • std::forward_as_tuple内联:0 个拷贝,0 个移动

最佳答案

您可以将 std::make_tuplestd::ref 一起使用:

auto make_comparison_object = [](const Foo& foo) {
    return std::make_tuple(
        std::ref(foo.s),
     // ^^^^^^^^
        -foo.x,
        std::accumulate(
            foo.z.begin(),
            foo.z.end(),
            0),
        foo.p ? std::make_optional(*foo.p) : std::nullopt);
};

关于c++ - 使用右值的比较函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51144329/

相关文章:

c++ - cin.ignore() 在程序中不起作用

c++ - 反转 C++ 元组

python - 比较 Pandas 中的两个 DataFrame

enums - 比较 Rust 中的嵌套枚举变体

c++ - Cocoa:将 NSApplication 集成到现有的 c++ 主循环中

c++ - 实现 C++ 状态机。如何解决 Wpmf 转换警告?

C++ 赋值副作用

list - 通过一次显示两个项目,根据用户的偏好对列表项目进行排名的最有效方法是什么?

c++ - 准确计算编译器在结构中添加的填充

python - 按数字和字母顺序对两个元素元组的列表进行排序