From 77259c6f332862273ef702d714711567713bb692 Mon Sep 17 00:00:00 2001 From: Jidong Xiao Date: Fri, 21 Feb 2025 12:55:02 -0500 Subject: [PATCH] adding the emplace_back and push_back example programs --- .../emplace_back/emplace_back_faster.cpp | 57 +++++++++++++ .../emplace_back/emplace_back_fastest.cpp | 55 ++++++++++++ .../emplace_back/emplace_back_slower.cpp | 47 ++++++++++ .../optimization/emplace_back/push_back.cpp | 44 ++++++++++ .../emplace_back/push_back_move.cpp | 54 ++++++++++++ .../emplace_back/push_back_reserve.cpp | 55 ++++++++++++ lectures/optimization/push_back/README.md | 85 +++++++++++++++++++ 7 files changed, 397 insertions(+) create mode 100644 lectures/optimization/emplace_back/emplace_back_faster.cpp create mode 100644 lectures/optimization/emplace_back/emplace_back_fastest.cpp create mode 100644 lectures/optimization/emplace_back/emplace_back_slower.cpp create mode 100644 lectures/optimization/emplace_back/push_back.cpp create mode 100644 lectures/optimization/emplace_back/push_back_move.cpp create mode 100644 lectures/optimization/emplace_back/push_back_reserve.cpp create mode 100644 lectures/optimization/push_back/README.md diff --git a/lectures/optimization/emplace_back/emplace_back_faster.cpp b/lectures/optimization/emplace_back/emplace_back_faster.cpp new file mode 100644 index 0000000..336ea00 --- /dev/null +++ b/lectures/optimization/emplace_back/emplace_back_faster.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include + +class LargeObject { +public: + // constructor allocating a large amount of memory + LargeObject(int size) { + size_ = size; + data_ = new char[size_]; + // initialize data with some values + std::memset(data_, 'A', size_); + } + + // copy constructor + LargeObject(const LargeObject& other) { + size_ = other.size_; + data_ = new char[size_]; + std::memcpy(data_, other.data_, size_); + } + + // move constructor + // marking functions as noexcept allows the compiler to make certain optimizations, + // knowing that the function won't emit exceptions. This can result in more efficient code generation. + LargeObject(LargeObject&& other) noexcept { + size_ = other.size_; + data_ = other.data_; + other.data_ = nullptr; + other.size_ = 0; + } + + // destructor + ~LargeObject() { + delete[] data_; + } + +private: + size_t size_; + char* data_; +}; + +int main() { + int numElements = 1000000; // number of elements + int dataSize = 1024; // size of each LargeObject's data + + std::vector vec; + + for (size_t i = 0; i < numElements; ++i) { + vec.emplace_back(dataSize); // move constructor would get called here + // when a std::vector exceeds its current capacity, + // it allocates a larger block of memory and moves existing elements to the new storage location. + // This reallocation process involves calling the move constructor for each existing element. + } + + return 0; +} diff --git a/lectures/optimization/emplace_back/emplace_back_fastest.cpp b/lectures/optimization/emplace_back/emplace_back_fastest.cpp new file mode 100644 index 0000000..9424bc9 --- /dev/null +++ b/lectures/optimization/emplace_back/emplace_back_fastest.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +class LargeObject { +public: + // constructor allocating a large amount of memory + LargeObject(int size) { + size_ = size; + data_ = new char[size_]; + // initialize data with some values + std::memset(data_, 'A', size_); + } + + // copy constructor + LargeObject(const LargeObject& other) { + size_ = other.size_; + data_ = new char[size_]; + std::memcpy(data_, other.data_, size_); + } + + // move constructor + // marking functions as noexcept allows the compiler to make certain optimizations, + // knowing that the function won't emit exceptions. This can result in more efficient code generation. + LargeObject(LargeObject&& other) noexcept { + size_ = other.size_; + data_ = other.data_; + other.data_ = nullptr; + other.size_ = 0; + } + + // destructor + ~LargeObject() { + delete[] data_; + } + +private: + size_t size_; + char* data_; +}; + +int main() { + int numElements = 1000000; // number of elements + int dataSize = 1024; // size of each LargeObject's data + + std::vector vec; + vec.reserve(numElements); + + for (size_t i = 0; i < numElements; ++i) { + vec.emplace_back(dataSize); + } + + return 0; +} diff --git a/lectures/optimization/emplace_back/emplace_back_slower.cpp b/lectures/optimization/emplace_back/emplace_back_slower.cpp new file mode 100644 index 0000000..f9b95e4 --- /dev/null +++ b/lectures/optimization/emplace_back/emplace_back_slower.cpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include + +class LargeObject { +public: + // constructor allocating a large amount of memory + LargeObject(int size) { + size_ = size; + data_ = new char[size_]; + // initialize data with some values + std::memset(data_, 'A', size_); + } + + // copy constructor + LargeObject(const LargeObject& other) { + size_ = other.size_; + data_ = new char[size_]; + std::memcpy(data_, other.data_, size_); + } + + // destructor + ~LargeObject() { + delete[] data_; + } + +private: + size_t size_; + char* data_; +}; + +int main() { + int numElements = 1000000; // number of elements + int dataSize = 1024; // size of each LargeObject's data + + std::vector vec; + + for (size_t i = 0; i < numElements; ++i) { + vec.emplace_back(dataSize); // copy constructor would get called here + // when a std::vector exceeds its current capacity, + // it allocates a larger block of memory and copies existing elements to the new storage location. + // this reallocation process involves calling the copy constructor for each existing element. + } + + return 0; +} diff --git a/lectures/optimization/emplace_back/push_back.cpp b/lectures/optimization/emplace_back/push_back.cpp new file mode 100644 index 0000000..e98042b --- /dev/null +++ b/lectures/optimization/emplace_back/push_back.cpp @@ -0,0 +1,44 @@ +#include +#include +#include + +class LargeObject { +public: + // constructor allocating a large amount of memory + LargeObject(int size) { + size_ = size; + data_ = new char[size_]; + // initialize data with some values + std::memset(data_, 'A', size_); + } + + // copy constructor + LargeObject(const LargeObject& other) { + size_ = other.size_; + data_ = new char[size_]; + std::memcpy(data_, other.data_, size_); + } + + // destructor + ~LargeObject() { + delete[] data_; + } + +private: + size_t size_; + char* data_; +}; + +int main() { + int numElements = 1000000; // number of elements + int dataSize = 1024; // size of each LargeObject's data + + std::vector vec; + + for (int i = 0; i < numElements; ++i) { + LargeObject obj(dataSize); // calls constructor + vec.push_back(obj); // calls copy constructor + } + + return 0; +} diff --git a/lectures/optimization/emplace_back/push_back_move.cpp b/lectures/optimization/emplace_back/push_back_move.cpp new file mode 100644 index 0000000..2752ef6 --- /dev/null +++ b/lectures/optimization/emplace_back/push_back_move.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +class LargeObject { +public: + // constructor allocating a large amount of memory + LargeObject(int size) { + size_ = size; + data_ = new char[size_]; + // initialize data with some values + std::memset(data_, 'A', size_); + } + + // copy constructor + LargeObject(const LargeObject& other) { + size_ = other.size_; + data_ = new char[size_]; + std::memcpy(data_, other.data_, size_); + } + + // move constructor + // marking functions as noexcept allows the compiler to make certain optimizations, + // knowing that the function won't emit exceptions. This can result in more efficient code generation. + LargeObject(LargeObject&& other) noexcept { + size_ = other.size_; + data_ = other.data_; + other.data_ = nullptr; + other.size_ = 0; + } + + // destructor + ~LargeObject() { + delete[] data_; + } + +private: + size_t size_; + char* data_; +}; + +int main() { + int numElements = 1000000; // number of elements + int dataSize = 1024; // size of each LargeObject's data + + std::vector vec; + + for (int i = 0; i < numElements; ++i) { + LargeObject obj(dataSize); // calls constructor + vec.push_back(std::move(obj)); // calls move constructor + } + + return 0; +} diff --git a/lectures/optimization/emplace_back/push_back_reserve.cpp b/lectures/optimization/emplace_back/push_back_reserve.cpp new file mode 100644 index 0000000..d47c429 --- /dev/null +++ b/lectures/optimization/emplace_back/push_back_reserve.cpp @@ -0,0 +1,55 @@ +#include +#include +#include + +class LargeObject { +public: + // constructor allocating a large amount of memory + LargeObject(int size) { + size_ = size; + data_ = new char[size_]; + // initialize data with some values + std::memset(data_, 'A', size_); + } + + // copy constructor + LargeObject(const LargeObject& other) { + size_ = other.size_; + data_ = new char[size_]; + std::memcpy(data_, other.data_, size_); + } + + // move constructor + // marking functions as noexcept allows the compiler to make certain optimizations, + // knowing that the function won't emit exceptions. This can result in more efficient code generation. + LargeObject(LargeObject&& other) noexcept { + size_ = other.size_; + data_ = other.data_; + other.data_ = nullptr; + other.size_ = 0; + } + + // destructor + ~LargeObject() { + delete[] data_; + } + +private: + size_t size_; + char* data_; +}; + +int main() { + int numElements = 1000000; // number of elements + int dataSize = 1024; // size of each LargeObject's data + + std::vector vec; + vec.reserve(numElements); + + for (int i = 0; i < numElements; ++i) { + LargeObject obj(dataSize); // calls constructor + vec.push_back(std::move(obj)); // calls move constructor + } + + return 0; +} diff --git a/lectures/optimization/push_back/README.md b/lectures/optimization/push_back/README.md new file mode 100644 index 0000000..e2e78c2 --- /dev/null +++ b/lectures/optimization/push_back/README.md @@ -0,0 +1,85 @@ +# `emplace_back` vs. `push_back` in C++ + +## Overview + +Both `push_back` and `emplace_back` are member functions of C++ standard library containers (e.g., `std::vector`, `std::deque`) used to add elements to the end of the container. However, they differ in how they construct and insert these elements. + +## `push_back` + +The `push_back` function adds an existing object to the end of the container. It requires the object to be constructed before being passed to the function. + +**Usage:** + +```cpp +std::vector vec; +MyClass obj(args); +vec.push_back(obj); // Adds a copy of 'obj' to the vector +``` + +If the object is movable, push_back can utilize move semantics: + +```cpp +vec.push_back(std::move(obj)); // Moves 'obj' into the vector +``` + +## `emplace_back` + +The emplace_back function constructs a new element in place at the end of the container. It forwards the provided arguments to the constructor of the element, eliminating the need for a temporary object. + +**Usage:** + +```cpp +std::vector vec; +vec.emplace_back(args); // Constructs 'MyClass' directly in the vector +``` + +This approach can improve performance by avoiding unnecessary copy or move operations, especially for complex objects. + +## Key Differences + +- **Object Construction:** + - `push_back`: Requires a fully constructed object. + - `emplace_back`: Constructs the object in place using provided arguments. + +- **Performance:** + - `push_back`: May involve copy or move operations, depending on whether the object is passed by value or moved. + - `emplace_back`: Potentially more efficient for complex objects, as it avoids extra copy or move operations. + +## When to Use + +- **Use `push_back`** when you have an existing object that you want to add to the container. + +- **Use `emplace_back`** when you want to construct a new object directly in the container, especially if the object's construction is complex or resource-intensive. + +## Example + +```cpp +#include +#include + +class MyClass { +public: + MyClass(int id, const std::string& name) : id_(id), name_(name) {} +private: + int id_; + std::string name_; +}; + +int main() { + std::vector vec; + + // Using push_back + MyClass obj(1, "Object1"); + vec.push_back(obj); // Adds a copy of 'obj' + + // Using emplace_back + vec.emplace_back(2, "Object2"); // Constructs 'MyClass(2, "Object2")' in place + + return 0; +} +``` + +In this example, emplace_back constructs the MyClass object directly within the vector, potentially reducing overhead compared to push_back, which adds a copy of an existing object. + +For a visual explanation and further insights, consider watching the following video: [![C++ From Scratch: push_back vs. emplace_back](https://img.youtube.com/vi/BbPWrkgj1I4/0.jpg)](https://www.youtube.com/watch?v=BbPWrkgj1I4) +