简体   繁体   中英

Initialize vector without copying and using move semantics

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?

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 theemplace_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");

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM