0

Is there a way where I could move the object GuitarSpec that is created in main instead of it being copied?

So here is the following example:- There is an Inventory class that has a list of guitars, and to add a guitar, there is a function called addGuitar that takes string, double and GuitarSpec object as an argument.

Inventory

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

addGuitar function

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

Guitar Constructor

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

Main function:-

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

Is there a way to move that GuitarSpec object instead of taking a copy of it, or any other better solution?

1 Answers1

1

When you consider moving only one parameter, you might get away with declaring function overloads, one of which would be moving from a temporary. Or you have design where oly temporaries are used, then why don't you follow an "emplace" strategy instead and create new object on-site?

But with two or more parameters moved the number of required overloads would be four, eight and so on. That's not good, in that case perfect forward might be more useful. An example of forwarding single parameter (of type Spec) in C++11 style:

#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);
}

You have to modify whole chain of responsibility to use that, starting with overloading Inventory's method:

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

Or using perfect forwarding, this template can copy OR move, when appropriate (example without 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));
}

Technically addGuitar might just be that if we don't want to bother about restricting interface by SFINAE, assuming we would always use it right and nothing wrong may happen (Murphy, put your hand down) if it's not a public interface. A bad assumption in large project with long life and multiple developers.

Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42