07_move_semantics.cpp

Download
cpp 315 lines 8.3 KB
  1/*
  2 * Move Semantics and Value Categories Demo
  3 *
  4 * Demonstrates:
  5 * - lvalue vs rvalue
  6 * - Move constructor and move assignment
  7 * - std::move and std::forward
  8 * - Perfect forwarding
  9 * - Rule of five / rule of zero
 10 * - Return value optimization (RVO)
 11 *
 12 * Compile: g++ -std=c++20 -Wall -Wextra 07_move_semantics.cpp -o move_semantics
 13 */
 14
 15#include <iostream>
 16#include <vector>
 17#include <string>
 18#include <utility>
 19#include <algorithm>
 20
 21// ============ Value Categories ============
 22void demo_value_categories() {
 23    std::cout << "\n=== Value Categories ===\n";
 24
 25    int x = 10;        // x is an lvalue
 26    int y = 20;        // y is an lvalue
 27
 28    int z = x + y;     // x + y is an rvalue (temporary)
 29
 30    std::cout << "lvalue x = " << x << "\n";
 31    std::cout << "lvalue y = " << y << "\n";
 32    std::cout << "rvalue (x + y) stored in z = " << z << "\n";
 33
 34    // int& ref1 = x + y;        // Error: can't bind lvalue ref to rvalue
 35    int&& ref2 = x + y;          // OK: rvalue reference
 36    std::cout << "rvalue reference ref2 = " << ref2 << "\n";
 37
 38    const int& ref3 = x + y;     // OK: const lvalue ref can bind to rvalue
 39    std::cout << "const lvalue reference ref3 = " << ref3 << "\n";
 40}
 41
 42// ============ Rule of Five ============
 43class Buffer {
 44private:
 45    size_t size_;
 46    int* data_;
 47    std::string name_;
 48
 49public:
 50    // Constructor
 51    Buffer(size_t size, const std::string& name)
 52        : size_(size), data_(new int[size]), name_(name) {
 53        std::cout << "  [Constructor: " << name_ << ", size=" << size_ << "]\n";
 54        for (size_t i = 0; i < size_; i++) {
 55            data_[i] = i;
 56        }
 57    }
 58
 59    // Destructor
 60    ~Buffer() {
 61        std::cout << "  [Destructor: " << name_ << "]\n";
 62        delete[] data_;
 63    }
 64
 65    // Copy constructor
 66    Buffer(const Buffer& other)
 67        : size_(other.size_), data_(new int[other.size_]),
 68          name_(other.name_ + "_copy") {
 69        std::cout << "  [Copy constructor: " << name_ << "]\n";
 70        std::copy(other.data_, other.data_ + size_, data_);
 71    }
 72
 73    // Copy assignment
 74    Buffer& operator=(const Buffer& other) {
 75        std::cout << "  [Copy assignment: " << other.name_ << " -> " << name_ << "]\n";
 76        if (this != &other) {
 77            delete[] data_;
 78            size_ = other.size_;
 79            data_ = new int[size_];
 80            std::copy(other.data_, other.data_ + size_, data_);
 81        }
 82        return *this;
 83    }
 84
 85    // Move constructor
 86    Buffer(Buffer&& other) noexcept
 87        : size_(other.size_), data_(other.data_),
 88          name_(std::move(other.name_) + "_moved") {
 89        std::cout << "  [Move constructor: " << name_ << "]\n";
 90        other.size_ = 0;
 91        other.data_ = nullptr;
 92    }
 93
 94    // Move assignment
 95    Buffer& operator=(Buffer&& other) noexcept {
 96        std::cout << "  [Move assignment: " << other.name_ << " -> " << name_ << "]\n";
 97        if (this != &other) {
 98            delete[] data_;
 99            size_ = other.size_;
100            data_ = other.data_;
101            name_ = std::move(other.name_) + "_moved";
102
103            other.size_ = 0;
104            other.data_ = nullptr;
105        }
106        return *this;
107    }
108
109    void print() const {
110        std::cout << "  Buffer '" << name_ << "' [size=" << size_ << "]: ";
111        if (data_) {
112            for (size_t i = 0; i < std::min(size_, size_t(5)); i++) {
113                std::cout << data_[i] << " ";
114            }
115            if (size_ > 5) std::cout << "...";
116        } else {
117            std::cout << "(empty)";
118        }
119        std::cout << "\n";
120    }
121};
122
123void demo_rule_of_five() {
124    std::cout << "\n=== Rule of Five ===\n";
125
126    Buffer buf1(10, "buf1");
127    buf1.print();
128
129    // Copy constructor
130    Buffer buf2 = buf1;
131    buf2.print();
132
133    // Move constructor
134    Buffer buf3 = std::move(buf1);
135    buf3.print();
136    buf1.print();  // buf1 is now empty
137
138    // Copy assignment
139    Buffer buf4(5, "buf4");
140    buf4 = buf2;
141    buf4.print();
142
143    // Move assignment
144    Buffer buf5(3, "buf5");
145    buf5 = std::move(buf3);
146    buf5.print();
147}
148
149// ============ std::move ============
150void demo_std_move() {
151    std::cout << "\n=== std::move ===\n";
152
153    std::string str1 = "Hello, World!";
154    std::cout << "str1 before move: '" << str1 << "'\n";
155
156    std::string str2 = std::move(str1);  // str1 is moved from
157    std::cout << "str1 after move: '" << str1 << "'\n";
158    std::cout << "str2 after move: '" << str2 << "'\n";
159
160    // Move into vector
161    std::vector<std::string> vec;
162    std::string str3 = "Move me!";
163    vec.push_back(std::move(str3));
164    std::cout << "str3 after push_back(move): '" << str3 << "'\n";
165    std::cout << "vec[0]: '" << vec[0] << "'\n";
166}
167
168// ============ Perfect Forwarding ============
169void process_lvalue(int& x) {
170    std::cout << "  Processing lvalue: " << x << "\n";
171}
172
173void process_rvalue(int&& x) {
174    std::cout << "  Processing rvalue: " << x << "\n";
175}
176
177// Universal reference with perfect forwarding
178template<typename T>
179void forward_to_process(T&& arg) {
180    if constexpr (std::is_lvalue_reference_v<T>) {
181        process_lvalue(arg);
182    } else {
183        process_rvalue(std::forward<T>(arg));
184    }
185}
186
187void demo_perfect_forwarding() {
188    std::cout << "\n=== Perfect Forwarding ===\n";
189
190    int x = 42;
191
192    forward_to_process(x);         // lvalue
193    forward_to_process(100);       // rvalue
194    forward_to_process(x + 10);    // rvalue
195}
196
197// ============ Rule of Zero ============
198class SimpleBuffer {
199private:
200    std::vector<int> data_;   // STL handles memory
201    std::string name_;
202
203public:
204    SimpleBuffer(size_t size, const std::string& name)
205        : data_(size), name_(name) {
206        std::cout << "  [SimpleBuffer constructor: " << name_ << "]\n";
207        for (size_t i = 0; i < size; i++) {
208            data_[i] = i;
209        }
210    }
211
212    // No destructor, copy/move constructors, or assignment operators needed!
213    // Compiler-generated versions work correctly
214
215    void print() const {
216        std::cout << "  SimpleBuffer '" << name_ << "' [size=" << data_.size() << "]\n";
217    }
218};
219
220void demo_rule_of_zero() {
221    std::cout << "\n=== Rule of Zero ===\n";
222
223    SimpleBuffer buf1(10, "simple1");
224    buf1.print();
225
226    // Copy (compiler-generated)
227    SimpleBuffer buf2 = buf1;
228    buf2.print();
229
230    // Move (compiler-generated)
231    SimpleBuffer buf3 = std::move(buf1);
232    buf3.print();
233
234    std::cout << "  No manual memory management needed!\n";
235}
236
237// ============ Return Value Optimization (RVO) ============
238Buffer create_buffer(size_t size, const std::string& name) {
239    return Buffer(size, name);  // RVO: no move/copy
240}
241
242void demo_rvo() {
243    std::cout << "\n=== Return Value Optimization (RVO) ===\n";
244
245    std::cout << "Creating buffer via function...\n";
246    Buffer buf = create_buffer(5, "rvo_buffer");
247    buf.print();
248    std::cout << "Note: No move/copy constructor called (RVO)!\n";
249}
250
251// ============ Move-Only Types ============
252class MoveOnly {
253private:
254    std::unique_ptr<int> data_;
255    std::string name_;
256
257public:
258    MoveOnly(int value, const std::string& name)
259        : data_(std::make_unique<int>(value)), name_(name) {
260        std::cout << "  [MoveOnly constructor: " << name_ << "]\n";
261    }
262
263    // Delete copy
264    MoveOnly(const MoveOnly&) = delete;
265    MoveOnly& operator=(const MoveOnly&) = delete;
266
267    // Default move
268    MoveOnly(MoveOnly&&) = default;
269    MoveOnly& operator=(MoveOnly&&) = default;
270
271    void print() const {
272        std::cout << "  MoveOnly '" << name_ << "': ";
273        if (data_) {
274            std::cout << *data_;
275        } else {
276            std::cout << "(empty)";
277        }
278        std::cout << "\n";
279    }
280};
281
282void demo_move_only() {
283    std::cout << "\n=== Move-Only Types ===\n";
284
285    MoveOnly obj1(42, "obj1");
286    obj1.print();
287
288    // MoveOnly obj2 = obj1;  // Error: copy deleted
289    MoveOnly obj2 = std::move(obj1);  // OK: move
290    obj2.print();
291    obj1.print();  // obj1 is now empty
292
293    // Store in vector
294    std::vector<MoveOnly> vec;
295    vec.push_back(std::move(obj2));
296    std::cout << "  Moved into vector\n";
297}
298
299// ============ Main ============
300int main() {
301    std::cout << "Move Semantics and Value Categories Demo\n";
302    std::cout << "==========================================\n";
303
304    demo_value_categories();
305    demo_rule_of_five();
306    demo_std_move();
307    demo_perfect_forwarding();
308    demo_rule_of_zero();
309    demo_rvo();
310    demo_move_only();
311
312    std::cout << "\nAll demos completed!\n";
313    return 0;
314}