将相同的值转发给两个或更多函数

时间:2016-09-04 22:44:04

标签: c++ c++11 forwarding-reference

使用转发引用时,转发它是一个坏主意 多个函数的值相同吗?请考虑以下代码:

template<typename Container>
constexpr auto
front(Container&& c)
-> typename Container::value_type
{ return std::forward<Container>(c).front(); }

template<typename Container>
constexpr auto
back(Container&& c)
-> typename Container::value_type
{ return std::forward<Container>(c).back(); }

template<typename Container>
constexpr auto
get_corner(Container&& c)
{
    return do_something(front(std::forward<Container(c)),
                        back(std::forward<Container>(c));
}

如果Container是左值引用,则该函数可以正常工作。但是,我担心rvalues传递给它的情况,因为一旦移动操作发生,该值将被无效。我的疑问是:在这种情况下,是否有正确的方法转发容器,而不会丢失值类别?

4 个答案:

答案 0 :(得分:14)

通常,同一函数转发相同参数两次是不合理的。除非它具体了解转发参数的接收者将做什么。

请记住:std::forward的行为可以等同于std::move的行为,具体取决于用户传入的参数。而xvalue的行为将取决于接收函数如何处理它。如果接收器采用非常量右值引用,则可能从该值移动。这会让你拿着一个移动的物体。如果它需要一个值,那么如果类型支持它,肯定会从中移动。

因此,除非您具有特定知识所使用的操作的预期行为,否则多次转发参数是不安全的。

答案 1 :(得分:4)

实际上没有std::begin的右值参考版本 - 我们只是(预留constexpr并返回值):

template <class C>
??? begin(C& );

template <class C>
??? begin(C const& );

对于左值容器,您获得iterator,对于右值容器,您获得const_iterator(或者任何特定于容器的等效容器最终都是)。

代码中的一个真正问题是返回decltype(auto)。对于左值容器,这很好 - 您将返回对其寿命超出函数的对象的引用。但对于右值容器,它会返回一个悬空参考。您希望为左值容器返回引用,为rvalue容器返回

最重要的是,forward将容器放入begin() / end()可能不是您想要做的。将select()的结果有条件地包装为移动迭代器会更有效。像this answer of mine这样的东西:

template <typename Container,
          typename V = decltype(*std::begin(std::declval<Container&>())),
          typename R = std::conditional_t<
              std::is_lvalue_reference<Container>::value,
              V,
              std::remove_reference_t<V>
              >
          >
constexpr R operator()(Container&& c)
{
    auto it = select(std::begin(c), std::end(c));
    return *make_forward_iterator<Container>(it);
}

表达所有这些可能不那么冗长。

答案 2 :(得分:3)

你可能会意识到你不希望std::move一个对象被传递给多个函数:

std::string s = "hello";
std::string hello1 = std::move(s);
std::string hello2 = std::move(s);  // hello2 != "hello"

forward的作用只是恢复参数传递给函数时的任何右值状态。

我们可以通过forward一个参数两次对具有移动效果的函数快速证明这是不好的做法:

#include <iostream>
#include <string>

struct S {
    std::string name_ = "defaulted";
    S() = default;
    S(const char* name) : name_(name) {}
    S(S&& rhs) { std::swap(name_, rhs.name_); name_ += " moved"; }
};

void fn(S s)
{
    std::cout << "fn(" << s.name_ << ")\n";
}

template<typename T>
void fwd_test(T&& t)
{
    fn(std::forward<T>(t));
    fn(std::forward<T>(t));
}

int main() {
    fwd_test(S("source"));
}

http://ideone.com/NRM8Ph

如果转发是安全的,我们应该看到fn(source moved)两次,但我们会看到:

fn(source moved)
fn(defaulted moved)

答案 3 :(得分:2)

总的来说,是的,这有潜在危险。

转发参数可确保如果通用引用参数接收的值是某种值的右值,则在转发时它将继续为右值。如果该值最终转发到通过移动来消耗值的函数(例如移动构造函数),则其内部状态可能无法在后续调用中使用。

如果您不转发参数,它(通常)不会有资格进行移动操作,因此您可以避免此类行为。

在您的情况下,frontback(自由函数和成员函数)不会对容器执行移动,因此您提供的具体示例应该是安全的。然而,这也表明没有理由转发容器,因为rvalue不会给左值赋予不同的处理 - 这是通过首先转发值来保持区别的唯一原因。