我是否经常使用std :: move()? [英] Am I using std::move() too often?

查看:115
本文介绍了我是否经常使用std :: move()?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我终于感觉到我了解现代C ++中的移动语义,并且它在编写代码的方式方面发生了翻天覆地的变化.现在,我正在开发一个使用依赖项注入的应用程序,并结合了我对移动语义学的新发现,但是最终我使用了std::move()太多了,以至于我担心自己使用不正确.

以前,如果要在对象中注入需要复制的依赖项,则可以这样编写我的构造函数:

class NeedsCopyOfFoo
{
public:
  NeedsCopyOfFoo(const Foo& foo)
    : m_myFoo{foo} {}
private:
  Foo m_myFoo;
};

现在,我的课程如下:

class NeedsCopyOfFoo
{
public:
  NeedsCopyOfFoo(Foo foo)
    : m_myFoo{std::move(foo)} {}
private:
  Foo m_myFoo;
};

我的设计中有一些类需要多达三到四个类类型的依赖关系,而最终我将它们全部移走了.显然,如果构造函数的调用者无法使用右值调用构造函数,但是在构造NeedsCopyOfFoo对象之后也将不使用依赖项,那么我也需要在其中使用std::move()以避免完全不必要的副本.

这是Modern C ++应该看起来的样子吗?鲍勃叔叔是否提到过经常使用std::move()"的代码味道吗?我反应过度了,是因为我还不习惯这种新风格吗?

解决方案

TL; DR:如果您不关心具有完美的性能,那么

Class(const Foo& foo, const Bar& bar, ...) : m_myFoo{foo}, m_myBar{bar}, ...{...} {}

是您的构造函数.它需要rvalues/lvalues,并且将花费您一个副本.它尽可能地使生活变得轻松愉快,而要过上轻松的生活还有很多话要说.


对于一个变量,我将有一个像这样的重载集

NeedsCopyOfFoo(Foo&& foo) : m_myFoo{std::move(foo)} {}
NeedsCopyOfFoo(const Foo& foo) : m_myFoo{foo} {}

根据将什么类型的对象传递给构造函数,此操作最多要进行一次复制或一次移动操作.这是您所能达到的最完美的.

不幸的是,这不能很好地扩展.当您开始添加更多要以相同方式处理的参数时,过载集将平方增长.这一点都不好玩,因为4参数构造函数需要16个重载才能完美.为了解决这个问题,我们可以使用转发构造函数,并使用 SFINAE 进行限制采用您想要的类型.那会给你一个像

的构造函数

template<typename T, 
         typename U, 
         std::enable_if_t<std::is_convertible_v<T, Foo> &&
                          std::is_convertible_v<U, Bar>, bool> = true>
Class(T&& foo, U&& bar) : 
    m_myFoo{std::forward<T>(foo)}, 
    m_myBar{std::forward<U>(bar)} {}

这可以为您带来最佳性能,但是正如您所看到的那样,它非常冗长,需要您对使用C ++有更多的了解.

I finally feel like I understand move semantics in Modern C++, and it's had a dramatic change on the way I write code. Right now, I'm working on an application that uses dependency injection and I'm incorporating my newfound knowledge of move semantics, but I end up using std::move() so much that I'm worried I'm using it incorrectly.

Previously, if I wanted to inject a dependency that I needed a copy of in my object, I'd write my constructor like this:

class NeedsCopyOfFoo
{
public:
  NeedsCopyOfFoo(const Foo& foo)
    : m_myFoo{foo} {}
private:
  Foo m_myFoo;
};

Now, my classes look like this:

class NeedsCopyOfFoo
{
public:
  NeedsCopyOfFoo(Foo foo)
    : m_myFoo{std::move(foo)} {}
private:
  Foo m_myFoo;
};

There are classes in my design which take as many as three or four class-type dependencies, and I end up moving them all. Obviously, If the caller of my constructor is not able to invoke the constructor with an rvalue, but also isn't going to use the dependency after constructing a NeedsCopyOfFoo object, I also need to use std::move() there, to avoid a completely unnecessary copy.

Is this the way that Modern C++ is supposed to look? Does Uncle Bob mention a code smell of "Uses std::move() too often"? Am I overreacting because I'm just not used to writing in this new style yet?

解决方案

TL;DR: If you don't care about having perfect performance then

Class(const Foo& foo, const Bar& bar, ...) : m_myFoo{foo}, m_myBar{bar}, ...{...} {}

is the constructor for you. It takes rvalues/lvalues and is going to cost you a copy. It's about as good as you can get and makes life easy, and there is a lot to be said for having an easy life.


For just one variable I would have an overload set like

NeedsCopyOfFoo(Foo&& foo) : m_myFoo{std::move(foo)} {}
NeedsCopyOfFoo(const Foo& foo) : m_myFoo{foo} {}

This cost at most one copy or one move operation depending on what type of object is passed to the constructor. This is as perfect as you can get.

Unfortunately this does not scale well. When you start to add more parameter that you want to handle the same way the overload set grows quadratically. That's not fun at all as a 4 parameter constructor would need 16 overloads to be perfect. To combat this we can use a forwarding constructor and limit it with SFINAE so it only takes the types you want. That would give you a constructor like

template<typename T, 
         typename U, 
         std::enable_if_t<std::is_convertible_v<T, Foo> &&
                          std::is_convertible_v<U, Bar>, bool> = true>
Class(T&& foo, U&& bar) : 
    m_myFoo{std::forward<T>(foo)}, 
    m_myBar{std::forward<U>(bar)} {}

This gives you the best performance, but as you can see it is quite verbose and requires you to know a lot more about C++ to work with.

这篇关于我是否经常使用std :: move()?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆