Compare commits

..

No commits in common. "b778751254480fd51e303f8d33804fe3c7c2fb91" and "bbb64dd8b7f73d605eef69896f5895cfb68174cd" have entirely different histories.

3 changed files with 27 additions and 197 deletions

View File

@ -4,7 +4,5 @@
env = DefaultEnvironment(tools=['default', 'compilation_db']) env = DefaultEnvironment(tools=['default', 'compilation_db'])
env.CompilationDatabase() env.CompilationDatabase()
env.Append(CCFLAGS=["-Wall", "-Wextra", "-Werror", "-pedantic"])
# Build the output program # Build the output program
Program('vector.out', Glob('*.cpp')) Program('vector.out', Glob('*.cpp'))

View File

@ -10,14 +10,13 @@ class Vector {
// If the needed capacity is known, it can be passed; other wise, a default // If the needed capacity is known, it can be passed; other wise, a default
// capacity is reserved. // capacity is reserved.
Vector(unsigned int capacity = 10) Vector(unsigned int capacity = 10)
: capacity(capacity), element_count(0), : capacity(capacity), element_count(0), data(new T[capacity]) {
data((T *)::operator new(capacity * sizeof(T))) {
} }
// Copy Constructor // Copy Constructor
Vector(const Vector &other) Vector(const Vector &other)
: capacity(other.capacity), element_count(other.element_count), : capacity(other.capacity), element_count(other.element_count),
data((T *)::operator new(capacity * sizeof(T))) { data(new T[capacity]) {
// `std::copy` is used because it is more flexible than `std::memcpy`, // `std::copy` is used because it is more flexible than `std::memcpy`,
// and the compiler will replace it with `memcpy` anyway if appropriate, // and the compiler will replace it with `memcpy` anyway if appropriate,
// so there is no performance loss. // so there is no performance loss.
@ -25,7 +24,7 @@ class Vector {
} }
// Move Constructor using the copy-and-swap-idiom // Move Constructor using the copy-and-swap-idiom
Vector(Vector &&other) : data(nullptr) { Vector(Vector &&other) : data(new T[capacity]) {
swap(*this, other); swap(*this, other);
} }
@ -43,10 +42,7 @@ class Vector {
// Destructor // Destructor
~Vector() { ~Vector() {
for (unsigned int i = 0; i < element_count; i++) { delete[] data;
data[i].~T();
}
::operator delete(data, capacity * sizeof(T));
} }
// Bracket Operator for accessing elements // Bracket Operator for accessing elements
@ -56,7 +52,7 @@ class Vector {
// Equals Operator: Returns true if the number of elements is identical and // Equals Operator: Returns true if the number of elements is identical and
// each of these elements are equal // each of these elements are equal
bool operator==(const Vector &other) const { bool operator==(const Vector &other) {
if (size() != other.size()) return false; if (size() != other.size()) return false;
for (unsigned int i = 0; i < size(); i++) { for (unsigned int i = 0; i < size(); i++) {
@ -108,22 +104,6 @@ class Vector {
} }
} }
// Erase an element by swapping the previously last element to its place
void erase_by_swap(unsigned int position) {
assert(position < element_count);
// Swap the last element to this position
std::swap(data[position], data[element_count - 1]);
// Delete the previous element at this position (now last)
data[element_count - 1].~T();
element_count--;
}
T *as_array() const {
return data;
}
// Returns the number of elements in the vector, regardless of the actually // Returns the number of elements in the vector, regardless of the actually
// reserved memory. // reserved memory.
unsigned int size() const { unsigned int size() const {
@ -151,9 +131,8 @@ class Vector {
// Resize the size of the vector to the given new_size // Resize the size of the vector to the given new_size
// If this decreases the size, some elements are deleted // If this decreases the size, some elements are deleted
// If this increases the size, some default-constructed elements are added. // If this increases the size, some default-constructed elements are added
// If no default constructor is available, a padder must be passed. void resize(unsigned int new_size) {
void resize(unsigned int new_size, const T &padder = T()) {
int difference = new_size - size(); int difference = new_size - size();
if (difference > 0) { if (difference > 0) {
@ -161,13 +140,13 @@ class Vector {
reallocate(new_size); reallocate(new_size);
} }
// Add additional items // Add additional default-constructed items
for (int i = 0; i < difference; i++) { for (int i = 0; i < difference; i++) {
new (data + element_count + i) T(padder); data[element_count + i] = T();
} }
} else if (difference < 0) { } else if (difference < 0) {
// Call the destructor on all excess items // Call the destructor on all excess items
for (int i = -1; i >= difference; i--) { for (int i = -1; i > difference; i--) {
data[element_count + i].~T(); data[element_count + i].~T();
} }
@ -180,26 +159,15 @@ class Vector {
} }
private: private:
unsigned int capacity; // Number of T objects the Vector could hold
unsigned int
element_count; // Number of T objects the Vector currently holds
T *data; T *data;
unsigned int element_count;
unsigned int capacity;
// Return a sensible capacity for the given size (including some bool is_using_excessive_memory() {
// padding).
unsigned int get_capacity_for_size(unsigned int size) const {
return size * 2 + 5;
}
// Return true if the Vector is holding much more memory (capacity) than it
// currently needs (element_count).
inline bool is_using_excessive_memory() const {
return 10 + element_count * 2 < capacity; return 10 + element_count * 2 < capacity;
} }
// Return true if the passed new_size would cause the Vector to exceed the bool new_size_exceeds_capacity(unsigned int new_size) {
// current capacity (meaning it must be expanded with `reallocate` first).
inline bool new_size_exceeds_capacity(unsigned int new_size) const {
return new_size > capacity; return new_size > capacity;
} }
@ -207,10 +175,16 @@ class Vector {
// Note that this function doesn't validate its input for optimization // Note that this function doesn't validate its input for optimization
// reasons! // reasons!
void reallocate(unsigned int new_size) { void reallocate(unsigned int new_size) {
T *new_data = (T *)::operator new(new_size * sizeof(T)); T *new_data = new T[new_size];
std::copy(data, data + element_count, new_data); std::copy(data, data + element_count, new_data);
::operator delete(data, capacity * sizeof(T)); delete[] data;
data = new_data; data = new_data;
} }
// Return a sensible capacity for the given size (including some
// padding).
unsigned int get_capacity_for_size(unsigned int size) {
return size * 2 + 5;
}
}; };

152
test.cpp
View File

@ -14,20 +14,12 @@ SCENARIO("Vector size and length are correct", "[vector]") {
} }
SCENARIO("The bracket operator returns the correct element", "[vector]") { SCENARIO("The bracket operator returns the correct element", "[vector]") {
GIVEN("A vector with some elements") { Vector<int> v(20);
Vector<int> v(20); v.push_back(1);
v.push_back(1); v.push_back(2);
v.push_back(2); v.push_back(3);
v.push_back(3);
THEN("The element in the middle must have the expected value") { REQUIRE(v[1] == 2);
REQUIRE(v[1] == 2);
}
THEN("The same value must be accessible using as_array") {
REQUIRE(v.as_array()[1] == 2);
}
}
} }
SCENARIO("Equality is returned correctly", "[vector]") { SCENARIO("Equality is returned correctly", "[vector]") {
@ -244,137 +236,3 @@ SCENARIO("Resizing a vector", "[vector]") {
} }
} }
} }
SCENARIO("Non-default constructor", "[vector]") {
GIVEN("A class object with a non-default constructor") {
class NonDefault {
public:
NonDefault(int a) : a(a) {
}
int a;
};
WHEN("Creating a vector and adding an element") {
Vector<NonDefault> non_default_vector;
non_default_vector.push_back(NonDefault(1));
THEN("The element should be accessible as usual") {
REQUIRE(non_default_vector[0].a == 1);
}
}
WHEN("Creating a vector and resizing it") {
Vector<NonDefault> non_default_vector;
non_default_vector.resize(50, NonDefault(42));
THEN("The new elements should correspond to the passed padder") {
REQUIRE(non_default_vector[49].a == 42);
}
}
}
}
SCENARIO("Erase by swap", "[vector]") {
GIVEN("A vector with some elements") {
Vector<int> v(3);
v.push_back(1);
v.push_back(2);
v.push_back(3);
WHEN("Erasing-by-swap the first element") {
v.erase_by_swap(0);
THEN("The size should have decrease by one") {
REQUIRE(v.size() == 2);
}
THEN("The previously last element should now be the first") {
REQUIRE(v[0] == 3);
}
}
}
}
unsigned int count = 0;
class ReferenceCounter {
public:
ReferenceCounter() {
count++;
}
ReferenceCounter(const ReferenceCounter &) {
count++;
}
ReferenceCounter(ReferenceCounter &&) {
count++;
}
ReferenceCounter &operator=(const ReferenceCounter &) {
return *this;
}
ReferenceCounter &operator=(ReferenceCounter &&) {
return *this;
}
~ReferenceCounter() {
count--;
}
};
SCENARIO("Constructors and Destructors are called as expected", "[vector]") {
GIVEN("A vector with a custom reference-counting class") {
Vector<ReferenceCounter> v(3);
v.push_back(ReferenceCounter());
v.push_back(ReferenceCounter());
v.push_back(ReferenceCounter());
REQUIRE(count == 3);
WHEN("Erasing the first element") {
v.erase(0);
THEN("The reference count should have decreased") {
REQUIRE(count == 2);
}
}
WHEN("Erasing by swap") {
v.erase_by_swap(0);
THEN("The reference count should have decreased") {
REQUIRE(count == 2);
}
}
WHEN("Resizing to 1") {
v.resize(1);
THEN("The reference count should be 1") {
REQUIRE(count == 1);
}
}
WHEN("Resizing to 10") {
v.resize(10);
THEN("The reference count should be 10") {
REQUIRE(count == 10);
}
}
WHEN("Move-constructing another vector") {
Vector<ReferenceCounter> v2 = std::move(v);
THEN("The reference count should have remained the same") {
REQUIRE(count == 3);
}
}
}
}