如何在 c++ 中移动对象而不是复制它?

How to move an object instead of copying it in c++?

提问人:Mohammad Hussein 提问时间:7/9/2021 最后编辑:Mohammad Hussein 更新时间:7/9/2021 访问量:362

问:

有没有办法让我移动在 main 中创建的对象 GuitarSpec,而不是复制它?

所以这里是以下示例:- 有一个 Inventory 类,它有一个吉他列表,要添加吉他,有一个名为 addGuitar 的函数,它将 string、double 和 GuitarSpec 对象作为参数。

库存

class Inventory {
 private:
  list<Guitar> inventory;
public:
  void addGuitar(const string &, double, const GuitarSpec &spec);

addGuitar 函数

void Inventory::addGuitar(const string &serialNumber, double price,
                          const GuitarSpec &spec) {
  inventory.emplace_back(serialNumber, price, spec);
}

Guitar 构造函数

Guitar::Guitar(const string& serialNumber, double price, const GuitarSpec &spec)
    : serialNumber{serialNumber}, price{price}, spec(spec) {
  cout << "Guitar Constructor" << endl;
}

主要功能:-

 Inventory inventory;
inventory.addGuitar(
      "1001", 200,
      GuitarSpec(toString(FEDER), "starocaster", toString(ELECTRIC),
                 toString(Wood::SIKTA), toString(Wood::SIKTA)));

有没有办法移动该 GuitarSpec 对象而不是获取它的副本,或任何其他更好的解决方案?

C++ OOP 复制构造函数 move-constructor

评论

1赞 Some programmer dude 7/9/2021
除非您有非常具体的需求和要求,否则默认容器类型应为 。你选择的原因是什么(我猜是)?std::vectorstd::list
0赞 Alan Birtles 7/9/2021
是可移动的吗?你试过实施动作吗?你遇到了什么问题?请展示一个最小的可重复示例GuitarSpec
0赞 Mohammad Hussein 7/9/2021
@Someprogrammerdude我实际上阅读了 Head First OOA 和 Design,该示例使用列表而不是向量,所以我选择了它。
1赞 Alan Birtles 7/9/2021
请展示一个最小的可重复示例
1赞 Swift - Friday Pie 7/9/2021
@Mohammad Hussein Spec 参数必须是可变的或通过右值引用传递,而不是通过常量引用传递,那么只要规范设计得当就足够了。查看 stackoverflow.com/questions/28595117/...您还可以将构造函数制作为模板并使用完美转发。GuitarSpec&&std::moveGuitar

答:

1赞 Swift - Friday Pie 7/9/2021 #1

当您考虑只移动一个参数时,您可能会避免声明函数重载,其中之一就是从临时参数移动。或者你有使用临时物的设计,那么你为什么不遵循“放置”策略,在现场创建新对象呢?

但是,如果移动了两个或多个参数,则所需的重载数将为 4 个、8 个,依此类推。这不好,在这种情况下,完美的前锋可能更有用。以 C++11 样式转发单个参数(类型)的示例:Spec

#include <iostream>
#include <utility>

struct Data {
    Data(const Data&) { std::cout << "Data copied\n"; }
    Data()  { std::cout << "Data created\n"; }
};

struct Spec {
    Data *ptr;
    
    Spec() : ptr(new Data()) {};
    
    Spec(const Spec& other) : ptr(new Data{*other.ptr}) {};
    
    Spec(Spec && other) : ptr(other.ptr) { 
          other.ptr = nullptr;
          std::cout << "Data moved\n";                      
    }
    
    Spec& operator=(const Spec& other) { ptr = new Data{*other.ptr};
                                        std::cout << "Data copied\n";
                                        return *this; }
    Spec& operator=(Spec&& other) { ptr = other.ptr; other.ptr = nullptr; 
                                    std::cout << "Data moved\n";  return *this; 
                                  }
    
    ~Spec() { delete ptr; }
};


struct foo {
    Spec d;
    
    template < typename T, std::enable_if_t<std::is_convertible<T, Spec>::value> * = nullptr>
    foo(T&& v) : d(std::forward<T>(v)) { }
    
    template <typename T>
    auto set_spec(T&& v) -> decltype(v = std::forward<Spec>(v), void())
    { d = std::forward<T>(v); }
};

int main()
{
    std::cout << "Move\n";
    foo a {Spec()};
    a.set_spec(Spec());
   
    
    std::cout << "Copy\n";
    Spec s;
    foo b {s};
    a.set_spec(s);
}

您必须修改整个责任链才能使用它,从重载 Inventory 的方法开始:

void addGuitar(const string &serialNumber, double price, GuitarSpec&& spec) {
     // move, do we want move string?
     inventory.emplace_back(serialNumber, price, std::move(spec));
}

或者使用完美转发,此模板可以在适当的时候复制或移动(例如没有 SFINAE):

template <class SN, class SP>
void addGuitar(SN&& serialNumber, double price, SP&& spec) 
{
  
      inventory.emplace_back(std::forward<std::string>(serialNumber), 
                             price, std::forward<GuitarSpec>(spec));
}

从技术上讲,可能只是如果我们不想费心通过 SFINAE 限制接口,假设我们总是正确地使用它,并且如果它不是公共接口,则不会发生任何错误(Murphy,放下你的手)。在具有长寿命和多个开发人员的大型项目中,这是一个糟糕的假设。addGuitar