Array and Vector

advertisement
Array and Vector
Slides from Hung Ngo
Designing a Vector Class
Amortized Analysis
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
3
In header file
Decide the operations “users” can do on a UBVector object
THE INTERFACE
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
4
UBVector’s Interface
// UBVector.h
#ifndef _UBVECTOR_H
#define _UBVECTOR_H
class UBVector {
public:
…
// accessors
std::string& operator[](size_t index);
const std::string& operator[](size_t index) const;
std::string& front();
const std::string& front() const;
std::string& back();
const std::string& back() const;
…
}
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
5
UBVector’s Interface
// UBVector.h
#ifndef _UBVECTOR_H
#define _UBVECTOR_H
class UBVector {
public:
…
// capacity
size_t size() const;
size_t capacity() const;
bool empty();
void reserve(size_t n);
…
}
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
6
UBVector’s Interface
// UBVector.h
#ifndef _UBVECTOR_H
#define _UBVECTOR_H
class UBVector {
public:
…
// modifiers
void push_back(const std::string& value);
void pop_back();
void insert(size_t position, const std::string& value);
void erase(size_t position);
void swap(UBVector& another_ubvec); // swap content
…
}
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
7
UBVector’s Interface
// UBVector.h
#ifndef _UBVECTOR_H
#define _UBVECTOR_H
class UBVector {
public:
// constructors and assignment operator
UBVector();
// default constructor
UBVector(size_t n);
// UBVector with n strings
UBVector(const UBVector&); // copy constructor
UBVector& operator=(const UBVector& another_ubvec);
// destructor
~UBVector();
…
private:
// ... more to come
}
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
8
Data structure used inside the class to implement the interface
THE INNER VIEW
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
9
Array
item_ptr[0]
“this”
item_ptr[2]
“is”
“a”
*(item_ptr+4)
“good”
“example”
item_ptr[1]
item_ptr
item_ptr + 3
string* item_ptr
The storage will have to be
dynamically allocated
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
10
UBVector’s Internal
class UBVector {
public:
// ... as above
size_t size() const { return num_items; }
size_t capacity() const { return current_capacity; }
bool
empty() { return num_items == 0; }
// ... as above
private:
size_t
size_t
static
string
};
5/28/2016
num_items;
current_capacity;
const size_t INITIAL_CAPACITY;
*item_ptr; // points to the start of the array
CSE 250, SUNY Buffalo, © Hung Q. Ngo
11
Pictorially
UBVector Object
size_t num_items;
size_t current_capacity;
string *item_ptr;
“this”
5/28/2016
“is”
“a”
“good”
CSE 250, SUNY Buffalo, © Hung Q. Ngo
“example”
12
Default constructor
Initialization list
Explicit constructor
CONSTRUCTORS & DESTRUCTOR
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
13
UBVector’s Constructors
// UBVector.h
#ifndef _UBVECTOR_H
#define _UBVECTOR_H
class UBVector {
public:
// constructors and assignment operator
UBVector();
// default constructor
UBVector(size_t n);
// UBVector with n strings
UBVector(const UBVector&); // copy constructor
UBVector& operator=(const UBVector& another_ubvec);
// destructor
~UBVector();
…
private:
// ... more to come
}
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
14
Default Constructor
// UBVector.cpp
#include <string>
#include "UBVector.h"
using namespace std;
// actually allocate the const member
const size_t UBVector::INITIAL_CAPACITY = 5;
UBVector::UBVector()
num_items
current_capacity
item_ptr
}
“”
5/28/2016
“”
{
= 0;
= INITIAL_CAPACITY;
= new string[current_capacity];
“”
“”
“”
CSE 250, SUNY Buffalo, © Hung Q. Ngo
15
UBVector’s Constructors
// UBVector.h
#ifndef _UBVECTOR_H
#define _UBVECTOR_H
class UBVector {
public:
// constructors and assignment operator
UBVector();
// default constructor
UBVector(size_t n = 0);
// UBVector with n strings
UBVector(const UBVector&); // copy constructor
UBVector& operator=(const UBVector& another_ubvec);
// destructor
~UBVector();
…
private:
// ... more to come
}
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
16
Default Constructor & Size Constructor
// UBVector.cpp
#include <string>
#include "UBVector.h"
using namespace std;
// actually allocate the const member
const size_t UBVector::INITIAL_CAPACITY = 5;
UBVector::UBVector(size_t n) {
num_items
= n;
current_capacity = max(n, INITIAL_CAPACITY);
item_ptr
= new string[current_capacity];
}
“”
5/28/2016
“”
“”
“”
“”
CSE 250, SUNY Buffalo, © Hung Q. Ngo
17
Initialization List
// UBVector.cpp
#include <string>
#include "UBVector.h"
using namespace std;
// actually allocate the const member
const size_t UBVector::INITIAL_CAPACITY = 5;
UBVector::UBVector(size_t n) :
num_items(n),
current_capacity(max(n, INITIAL_CAPACITY)),
item_ptr(new string[max(n, INITIAL_CAPACITY)])
{ /* constructor has empty body */ }
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
18
Initialization List Can Be More Efficient
T x;
x = expression;
•
•
•
•
Expression is evaluated, object type T created
Assignment operator for T is called
Temporary object is destroyed
If x = expression is in constructor, a copy of
x is created before doing assignment
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
19
Destructor
UBVector::~UBVector() {
delete [] item_ptr;
}
• I’m using raw pointer here
• In C++11, you can use unique_ptr template
class, then no need to delete
– delete [] automatically called when it goes out of
scope
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
20
Subscripting operator
Front
Back
Push_back
Reserve
IMPLEMENTATION OF SOME
SIMPLE METHODS
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
21
Subscripting operator – const
string& UBVector::operator[](size_t index) {
if (index < num_items)
return item_ptr[index];
else
throw out_of_range("index is out of range");
}
Return a reference so we can do ubvec[12] = “abc”; later
const string& UBVector::operator[](size_t index) const {
if (index < num_items)
return item_ptr[index];
else
throw out_of_range("index is out of range");
}
This const means you can’t modify the returned cell
This const says this function doesn’t modify data members
const version used when the object itself is const
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
22
front() and back()
string& UBVector::front() {
return (*this)[0];
}
const string& UBVector::front() const {
return (*this)[0];
}
string& UBVector::back() {
return (*this)[num_items-1];
}
const string& UBVector::back() const {
return (*this)[num_items-1];
}
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
23
Unit Testing
// driver.cpp: test the UBVector class
#include <iostream>
#include <stdexcept>
#include "UBVector.h"
using namespace std;
int main() {
UBVector ubv(3);
ubv[0] = "this"; ubv[1] = "is"; ubv[2] = "good!";
cout << ubv[0] << " " << ubv[1] << " " << ubv[2] << endl;
ubv.front() = "THIS";
ubv.back() = "GOOD!";
cout << ubv.front() << " " << ubv[1] << " " << ubv[2] << endl;
try {
cout << ubv[4] << endl;
} catch (exception &e) {
cout << e.what() << endl;
}
}
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
24
push_back()
void UBVector::push_back(const string& item) {
if (num_items == current_capacity)
reserve(2*current_capacity);
item_ptr[num_items++] = item;
}
“ab”
“cd”
“ef”
“gh”
“ijk”
Memory Is Fragmented
item_ptr
“ab”
“cd”
occupied
“ef”
“gh”
new_item_ptr = new string[2*current_capacity]
reserve()
void UBVector::reserve(size_t n) {
if (n > current_capacity) {
current_capacity = max(n, 2*current_capacity);
string *new_data_ptr = new string[current_capacity];
for (size_t i=0; i<num_items; i++) {
new_data_ptr[i] = item_ptr[i];
}
delete [] item_ptr;
item_ptr = new_data_ptr;
}
}
Amortized analysis: The simple case
• Each push_back takes O(1)-time on average
• Capacities vs cost vs credits
1 2 3 4 5 6 7 8 9 10 11 12 13 14 …
1 1 1 1 1 6 1 1 1 1 11 1 1 1
2 2 2 2 2 2 2 2 2 2
2
When to shrink capacity
Implementations of erase and pop_back
STL behaviors on capacity
Amortized analysis
ERASE, POP_BACK & AMORTIZED
ANALYSIS
When Do We Shrink UBVector’s
Capacity?
• Option 1:
– Cut the capacity in half if the number of items
stored is < half the current capacity
• Option 2:
– Cut the capacity in half if the number of items
stored is < ¼ of the current capacity
Let’s See How C++’s STL Does It
#include <iostream>
#include <vector>
using namespace std;
int main() {
static const size_t C = 20;
vector<int> vec;
cout << "Initial Capacity = " << vec.capacity() << endl;
for (size_t i=0; i<C; i++) {
vec.push_back(i);
cout << ”C[" << i << ”]=" << vec.capacity() << " ";
}
for (size_t i=C; i>0; i--) {
vec.pop_back();
cout << ”C[" << i-1 << ”]=" << vec.capacity() << " ";
}
cout << endl;
}
Result
Initial capacity = 0
C[0] = 1
C[4] = 8
C[8] = 16
C[12] = 16
C[16] = 32
C[1] = 2
C[5] = 8
C[9] = 16
C[13] = 16
C[17] = 32
C[2] = 4
C[6] = 8
C[10] = 16
C[14] = 16
C[18] = 32
C[3] = 4
C[7] = 8
C[11] = 16
C[15] = 16
C[19] = 32
C[19] = 32
C[15] = 32
C[11] = 32
C[7] = 32
C[3] = 32
C[18] = 32
C[14] = 32
C[10] = 32
C[6] = 32
C[2] = 32
C[17] = 32
C[13] = 32
C[9] = 32
C[5] = 32
C[1] = 32
C[16] = 32
C[12] = 32
C[8] = 32
C[4] = 32
C[0] = 32
So How To Shrink the Capacity of an
STL Vector?
int main() {
static const size_t C
vector<int> vec;
cout << "Initial capa
for (size_t i=0; i<C;
vec.push_back(i);
cout << ”C[" << i
}
= 20;
= " << vec.capacity() << endl;
i++) {
<< ”]=" << vec.capacity() << " ";
cout << endl;
for (size_t i=C; i>0; i--) {
vec.pop_back();
vector<int>(vec).swap(vec); // the swap trick!
cout << ”C[" << i-1 << ”]=" << vec.capacity() << “ ";
}
cout << endl;
}
C++11’s vector has a shrink_to_fit() member function
Result
Initial capacity = 0
C[0] = 1 C[1] = 2 C[2] = 4 C[3] = 4 C[4] = 8 C[5] = 8
C[6] = 8 C[7] = 8 C[8] = 16 C[9] = 16 C[10] = 16
C[11] = 16 C[12] = 16 C[13] = 16 C[14] = 16 C[15] = 16
C[16] = 32 C[17] = 32 C[18] = 32 C[19] = 32
C[19] = 19 C[18] = 18 C[17] = 17 C[16] = 16 C[15] = 15
C[14] = 14 C[13] = 13 C[12] = 12 C[11] = 11 C[10] = 10
C[9] = 9 C[8] = 8 C[7] = 7 C[6] = 6 C[5] = 5 C[4] = 4
C[3] = 3 C[2] = 2 C[1] = 1 C[0] = 0
erase() and pop_back()
void UBVector::erase(size_t position)
{
if (position < num_items) {
--num_items;
for (size_t i=position; i<num_items; ++i)
item_ptr[i] = item_ptr[i+1];
item_ptr[num_items].~string();
// explicit destructor call
new (item_ptr + num_items) string(); // placement new
}
}
void UBVector::pop_back() {
erase(num_items-1);
}
Remember to swap pointers instead of contents
INSERT AND SWAP
Insert()
void UBVector::insert(
size_t position,
const string& new_item)
{
if (num_items == current_capacity)
reserve(2*current_capacity);
for (size_t i=num_items; i>position; --i)
item_ptr[i] = item_ptr[i-1];
item_ptr[position] = new_item;
++num_items;
}
Swap()
void UBVector::swap(UBVector& the_other) {
std::swap(num_items,
the_other.num_items);
std::swap(current_capacity,
the_other.current_capacity);
std::swap(item_ptr, the_other.item_ptr);
}
Copy constructor
Assignment operator
Destructor
THE RULE OF THREE
The Copy Constructor
// UBVector.h
#ifndef _UBVECTOR_H
#define _UBVECTOR_H
class UBVector {
public:
// constructors and assignment operator
explicit UBVector(size_t n = 0);
// UBVector with n strings
UBVector(const UBVector&); // copy constructor
UBVector& operator=(const UBVector& another_ubvec);
// destructor
~UBVector();
…
private:
// ... more to come
}
5/28/2016
CSE 250, SUNY Buffalo, © Hung Q. Ngo
40
When is a Copy Constructor Called?
1. Declare and initialize a variable
– UBVector vec1(vec2);
2. Pass an object by value
– void Foo(UBVector ubv);
– UBVector a(400);
– foo(a);
3. Return an object by value
– Watch out for Return Value Optimization
The Rule of Three
• If a class defines one of the following, it
should define all three:
– Copy constructor
– (Copy) Assignment operator
– Destructor
• No need if there’s no dynamic memory
• C++11: rule of five. Let’s not go there.
What Happens if We Don’t Define
Them
int main() {
UBVector a(4);
a[0] = "this"; a[1] = "is";
a[2] = "very"; a[3] = "interesting";
print_vec(a);
UBVector b(a); // equivalent to UBVector b = a;
a[2] = "NOT";
print_vec(b);
}
this is very interesting
this is NOT interesting
a.out(3229) malloc: *** error for object 0x1067009f0: pointer being freed was not
allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6
Default Behavior Compiler Gives Us
• If we don’t define them, the compiler support
default versions
• Copy Constructor & Assignment Operator
– Member-wise copy
• Destructor
– Destruct all data members
Assignment Operator – Code Reuse!!!
UBVector& UBVector::operator=(const UBVector& the_other) {
// uses (deep) copy constructor here
UBVector temp(the_other);
swap(temp);
return *this;
}
Copy Constructor
UBVector::UBVector(const UBVector& other) :
num_items(other.num_items),
current_capacity(other.num_items),
item_ptr(new string[other.num_items])
{
for (size_t i=0; i<num_items; ++i)
item_ptr[i] = other.item_ptr[i];
}
Download