Why vector hold a class type will call the copy co

2020-04-18 03:28发布

I have the following code:

#include <iostream>
using std::cin; using std::cout; using std::endl;
#include <vector>
using std::vector;

class Quote {
public:
    Quote() = default;
    Quote(const std::string &book, double sales_price):
                     bookNo(book), price(sales_price) {  }
    // Quote(const Quote&) = default;  // memberwise copy
    Quote(const Quote &orig): bookNo(orig.bookNo), price(orig.price) {
        cout << orig.isbn() << endl;
        cout << "called Quote(const Quote &)" << endl;
    }
    Quote& operator=(const Quote&) = default;   // copy assign

    std::string isbn() const { return bookNo; }
    virtual double net_price(std::size_t n) const
                { cout << "Quote::net_price\n"; return n * price; }
    virtual void debug() const { cout << bookNo << ' ' << price << endl; }
    virtual ~Quote() = default;
private:
    std::string bookNo; // ISBN number of this item
protected:
    double price = 0.0; // normal, undiscouted price
};

int main(int argc, char *argv[]) {
    vector<Quote> basket;
    basket.push_back(Quote("0-201-82470-1", 50));
    basket.push_back(Quote("0-201-82XXXXX", 30));
    cout << "\ntraverse bakset" << endl;
    for (const auto &v : basket)
        v.debug();
}

After I compile the above code and run, the result is:

0-201-82470-1
called Quote(const Quote &)
0-201-82XXXXX
called Quote(const Quote &)
0-201-82470-1
called Quote(const Quote &)

traverse bakset
0-201-82470-1 50
0-201-82XXXXX 30

According to when copy constructor is called, it will be called twice because I just have pushed two elements when I push_back() to a vector. But why it is called three times displayed in the above result.
However, according to the for-loop in main, the element of the vector is right.

Why the copy constructor is called one more time when pushed to a vector? And is there anything wrong with my defined copy constructor?

2条回答
Ridiculous、
2楼-- · 2020-04-18 04:01

As per C++ language open standard draft n3690.pdf regarding vector capacity. Please see the bold italicized statement.

23.3.7.3 vector capacity [vector.capacity] size_type capacity() const noexcept; 1 Returns: The total number of elements that the vector can hold without requiring reallocation. void reserve(size_type n); 2 Requires: T shall be MoveInsertable into *this. 3 Effects: A directive that informs a vector of a planned change in size, so that it can manage the storage allocation accordingly. After reserve(), capacity() is greater or equal to the argument of reserve if reallocation happens; and equal to the previous value of capacity() otherwise. "Reallocation happens at this point if and only if the current capacity is less than the argument of reserve()". If an exception is thrown other than by the move constructor of a non-CopyInsertable type, there are no effects

Also from Scott Meyers "Effective C++ Digital Collection: 140 Ways to Improve Your Programming", under item 14 Item 14.

Use reserve to avoid unnecessary reallocations. One of the most marvelous things about STL containers is that they automatically grow to accommodate as much data as you put into them, provided only that you don't exceed their maximum size. (To discover this maximum, just call the aptly named max_size member function.) For vector and string, growth is handled by doing the moral equivalent of a realloc whenever more space is needed. This realloc-like operation has four parts: 1. Allocate a new block of memory that is some multiple of the container's current capacity. In most implementations, vector and string capacities grow by a factor of between 1.5 and 2 each time.

As suggested by "songyuanyao" one should reserve the size(if it is known in advance) to avoid frequent reallocation.

查看更多
▲ chillily
3楼-- · 2020-04-18 04:03

When the push_back is called at the 2nd time, reallocation happened. (More precisely it happens when the new size() is greater than capacity().) Then the old underlying storage of the vector will be destroyed and the new one will be allocated, and elements need to be copied to the new storage, which cause the copy constructor to be called.

You can use reserve to avoid reallocation. e.g.

vector<Quote> basket;
basket.reserve(2);
basket.push_back(Quote("0-201-82470-1", 50));
basket.push_back(Quote("0-201-82XXXXX", 30)); // no reallocation here
查看更多
登录 后发表回答