0

CPP 核心指南 F45 指出:

不要返回 T&&。

我想创建一些类,并通过一系列修改类的函数传递类。然后我将评估该类的一些成员,或者将该类存储在某个容器中,例如std::vector.

我试图以这样一种方式做到这一点,即该类只构造一次,然后被销毁或移动,并且移动的副本被销毁,然后存储的副本被销毁(当我存储它的容器被销毁时) .

本质上,我想这样做:

// some class
class foo{}

// construct a foo, with some parameters, and modify it somehow
auto f1 = modify_foo(foo(x, y, z));
// modify the foo some more
auto f2 = modify_foo(f1);
// modify the foo some more
auto f3 = modify_foo(f2);

// use some element of modified foo
auto v = f3.getx();

// maybe store the modified foo in a vector or some other container
vector<foo> vf;
vf.emplace_back(f3);

应该可以构造foo恰好一次,并foo通过任意数量的修改函数移动构造,然后销毁foo恰好一次。

如果将 存储foo在 a 中vector,则必须进行额外的复制/移动和销毁。

我可以实现这种行为,但是如果不将此签名用于修改功能,我想不出任何方法:

foo&& modify_foo(foo&& in);

这是似乎可以满足我要求的测试代码:

#include <iostream>
#include <functional>
#include <vector>

// A SIMPLE CLASS WITH INSTRUMENTED CONSTRUCTORS
class foo {

public:
    // default constructor
    foo() {
        std::cout << "default construct\n";
    }

    // copy constructor
    foo(foo const &in) : x{ in.x } {
        std::cout << "copy construct\n";
    };

    // copy assignment
    foo& operator=(foo const& in) {
        x = in.x;
        std::cout << "copy assignment\n";
    }

    // move constructor
    foo(foo&& in) noexcept : x(std::move(in.x)) {
        std::cout << "move constructor\n";
    }

    // move assignment
    foo& operator=(foo&& in) noexcept {
        x = std::move(in.x);
        std::cout << "move assignment\n";
        return *this;
    }

    // destructor
    ~foo() {
        std::cout << "destructor\n";
    }

    void inc() {
        ++x;
    }

    int getx() { return x; };

private:

    int x{ 0 };

};

现在一个函数将接受foo&&、修改foo、返回foo&&

// A SIMPLE FUNCTION THAT TAKES foo&&, modifies something, returns foo&&
foo&& modify(foo&& in) {
    in.inc();
    return std::move(in);
}

现在使用类和修改功能:

int main(){

    // construct a foo, modify it, return it as foo&&
    auto&& foo1 = modify(foo());

    // modify foo some more and return it
    auto&& foo2 = modify(std::move(foo1));

    // modify foo some more and return it
    auto&& foo3 = modify(std::move(foo2));

    // do something with the modified foo:

    std::cout << foo3.getx();
}

这将完全符合我的要求。它将调用一次构造函数,正确地打印3,并调用一次析构函数。

如果我做同样的事情,除了添加这个:

std::vector<foo> fv;
fv.emplace_back(std::move(foo3));

它将添加一个移动构造,并在vector超出范围时破坏另一个。

这正是我想要的行为,而且我还没有想出任何其他方法可以在不foo&&从修饰符返回的情况下到达那里,并使用auto&&我的中间变量,并使用std::move()传递给后续调用的参数modify

这种模式对我非常有用。困扰我的是,我无法使用 CPP 核心指南 F.45 解决这个问题。该指南确实说:

当对临时对象的引用“向下”传递给被调用者时,返回右值引用就可以了;那么临时保证比函数调用更长寿......

也许这就是我正在做的?

我的问题:

  1. 我正在做的事情有什么根本错误或未定义的吗?

  2. 当我这样做auto&&并将鼠标悬停在 上时foo1,它将显示为foo&&. 我仍然必须将 with 包装foo1起来std::move(foo1)才能让 modify 函数将其接受为foo&&. 我觉得这有点奇怪。需要这种语法的原因是什么?

4

1 回答 1

0

正如 NathanOliver 正确指出的那样,尝试使用右值引用会留下对在函数生命结束时被销毁的对象的悬空引用。

我缺少的一块拼图是使用'auto&',而不是'auto'从函数返回 ref 时:

// function taking lvalue ref, returning lvalue ref
foo& modify(foo& in) {
    in.inc();
    return in;
}

{

    auto f =  foo{}; // constructed here

    auto f1 = modify(f); // <-- BAD!!! copy construct occurs here.

    auto& f2 = modify(f); // <-- BETTER - no copy here

} // destruct, destruct

如果我使用 auto& 来捕获从“修改”返回的左值 ref,则不会制作任何副本。然后我得到了我想要的行为。一种构造,一种破坏。

{
    // construct a foo
    foo foo1{};

    // modify some number of times
    auto& foo2 = modify(std::move(foo1));
    auto& foo3 = modify(std::move(foo2));
    auto& foo4 = modify(std::move(foo3));

    std::cout << foo4.getx();
} // 1 destruct here
于 2020-11-24T12:36:35.860 回答