Home > database >  Initialize vector without copying and using move semantics
Initialize vector without copying and using move semantics

Time:04-26

Is there a way to avoid copying when initializing a vector?

The code below will produce the following output.

#include <iostream>
#include <vector>
using namespace std;

struct Ticker {
    std::string m_ticker;
    
    Ticker() {
        std::cout << "Default constructor" << std::endl;
        }
    Ticker(const std::string& ticker) 
    : m_ticker(ticker)
    {
        std::cout << "Parametrized constructor" << std::endl;
    }
    Ticker(Ticker&& other) 
    {
        std::cout << "Move constructor" << std::endl;
        m_ticker = other.m_ticker;
        other.m_ticker = "";
    }
    Ticker(const Ticker& x) 
    {
        std::cout << "Copy constructor" << std::endl;
        m_ticker = x.m_ticker;
    }
    ~Ticker() 
    {
    std::cout << "Destructor" << std::endl;
    }
    
    friend std::ostream& operator << (std::ostream& os, const Ticker& dr);
};

std::ostream& operator << (std::ostream& os, const Ticker& dr)
{
  os << "|" << dr.m_ticker << "|";
   return os;
}
    

int main() {
  std::vector<Ticker> table = std::move(std::vector<Ticker>{std::move(Ticker("MSFT")), std::move(Ticker("TSL"))});
  for (const auto& row: table)
  {
    std::cout << row << std::endl;
  }
  return 0;
}

This produces the following output:

Parametrized constructor
Move constructor
Parametrized constructor
Move constructor
Copy constructor
Copy constructor
Destructor
Destructor
Destructor
Destructor
|MSFT|
|TSL|
Destructor
Destructor

Is there a way to avoid the copy constructor and initialize in-place or just move without copying?

CodePudding user response:

If you use

std::vector<Ticker> table = std::vector<Ticker>{Ticker("MSFT"), Ticker("TSL")};

You will get

Parametrized constructor
Parametrized constructor
Copy constructor
Copy constructor
Destructor
Destructor
|MSFT|
|TSL|
Destructor
Destructor

Which has 4 constructor calls instead of the 6 you currently have. 2 of those calls are for Ticker("MSFT") and Ticker("TSL") and then the additional two copies are because initializer lists store the elements in them as const, so they have to be copied into the vector as you can't move from a const object.

To get the bare minimum of 2 constructor calls you'll need to use the emplace_back member function like

std::vector<Ticker> table;      // create empty vector
table.reserve(2);               // allocate space for 2 Tickers but create nothing
table.emplace_back("MSFT");     // directly construct from "MSFT" in the reserved space
table.emplace_back("TSL");      // directly construct from "TSL" in the reserved space

which has the output of

Parametrized constructor
Parametrized constructor
|MSFT|
|TSL|
Destructor
Destructor

If you want a syntax like std::vector<Ticker> table = std::vector<Ticker>{Ticker("MSFT"), Ticker("TSL")};, but without the extra overhead, you could wrap the emplace_back solution in a factory function like

template <typename T, typename... Args> 
auto make_vector(Args&&... args)
{
    std::vector<T> data;
    data.reserve(sizeof...(Args));
    (data.emplace_back(std::forward<Args>(args)), ...);
    return data;
}

and then you would use it like

auto table = make_vector<Ticker>("MSFT", "TSL");
  • Related