在this answer中,他们建议使用以下代码:
#include <iostream>
template <typename F>
class Finally {
F f;
public:
template <typename Func>
Finally(Func&& func) : f(std::forward<Func>(func)) {}
~Finally() { f(); }
Finally(const Finally&) = delete;
Finally(Finally&&) = delete;
Finally& operator =(const Finally&) = delete;
Finally& operator =(Finally&&) = delete;
};
template <typename F>
Finally<F> make_finally(F&& f)
{
return Finally<F>{ std::forward<F>(f) }; // This doesn't compile
//This compiles: return { std::forward<F>(f) };
}
int main()
{
auto&& doFinally = make_finally([&] { std::cout<<", world!\n"; });
std::cout << "Hello";
}
作者链接到一个使用Clang ++ / G ++编译的演示。但是,在MSVC ++ 2017中,此代码无法为我编译。
错误消息是:
source_file.cpp(20): error C2280: 'Finally<main::<lambda_9000fb389e10855198e7a01ce16ffa3d>>::Finally(Finally<main::<lambda_9000fb389e10855198e7a01ce16ffa3d>> &&)': attempting to reference a deleted function
source_file.cpp(12): note: see declaration of 'Finally<main::<lambda_9000fb389e10855198e7a01ce16ffa3d>>::Finally'
source_file.cpp(26): note: see reference to function template instantiation 'Finally<main::<lambda_9000fb389e10855198e7a01ce16ffa3d>> make_finally<main::<lambda_9000fb389e10855198e7a01ce16ffa3d>>(F &&)' being compiled
with
[
F=main::<lambda_9000fb389e10855198e7a01ce16ffa3d>
]
那么编译的return { std::forward<F>(f) };
和return Finally<F>{ std::forward<F>(f) };
之间有什么区别,但是另一个没有?
答案 0 :(得分:4)
那么
之间有什么区别return { std::forward<F>(f) };
和return Finally<F>{ std::forward<F>(f) };
前者初始化返回对象。所以当你有:
X foo() { return {a, b, c}; }
auto&& res = foo();
这将构建一个X
,然后将res
绑定到它。根本没有任何移动。感谢RVO,这不是一个可以省略的举动,而不是因为C ++ 17中使用新值类别保证省略而不会采取行动。我们决不会考虑移动任何东西。只有一个X
(即使在C ++ 11中,即使是-fno-elide-constructors
,因为没有构造函数调用elide)。
相比之下,这个:
X bar() { return X{a, b, c}; }
auto&& res2 = bar();
在C ++ 17之前,将创建一个临时X
,然后将其移至bar()
的返回值。这将成为RVO的候选人,这一举动肯定会被淘汰,但为了避免这一举动,实际上必须采取行动。在我们的例子中,X
的移动构造函数被删除,因此这段代码是错误的。这导致理查德史密斯在P0135R0中的评论最好地描述了这种情况:
// error, can't perform the move you didn't want, // even though compiler would not actually call it
在 C ++ 17之后,没有临时的。我们有一个类型为X
的prvalue,我们用它来初始化bar()
的返回对象,所以我们最终只是从prvalue的初始化器初始化返回对象。该行为等同于上面的foo()
示例。不会采取任何行动。