使用转发引用时,转发它是一个坏主意 多个函数的值相同吗?请考虑以下代码:
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传递给它的情况,因为一旦移动操作发生,该值将被无效。我的疑问是:在这种情况下,是否有正确的方法转发容器,而不会丢失值类别?
答案 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"));
}
如果转发是安全的,我们应该看到fn(source moved)
两次,但我们会看到:
fn(source moved)
fn(defaulted moved)
答案 3 :(得分:2)
总的来说,是的,这有潜在危险。
转发参数可确保如果通用引用参数接收的值是某种值的右值,则在转发时它将继续为右值。如果该值最终转发到通过移动来消耗值的函数(例如移动构造函数),则其内部状态可能无法在后续调用中使用。
如果您不转发参数,它(通常)不会有资格进行移动操作,因此您可以避免此类行为。
在您的情况下,front
和back
(自由函数和成员函数)不会对容器执行移动,因此您提供的具体示例应该是安全的。然而,这也表明没有理由转发容器,因为rvalue不会给左值赋予不同的处理 - 这是通过首先转发值来保持区别的唯一原因。