Passing Arguments: Value, Reference and Move
How you declare a parameter decides whether the function gets a copy, read-only access, or the right to change the caller's object. A few simple rules cover almost every case and keep C++ programs both correct and fast.
The guidelines
| The function… | Parameter type | Example |
|---|---|---|
| Only reads a cheap value (numbers, characters, small structs) | By value: T | double area(double w, double h) |
| Only reads an expensive object (strings, vectors, big structs) | By const reference: const T& | int count(const std::string& text) |
| Modifies the caller's object | By reference: T& | void sort_names(std::vector<std::string>& v) |
| Needs its own copy to keep (e.g. to store) | By value, then std::move it | void set_name(std::string n) |
| Reads a string or array of any kind | std::string_view / std::span | lesson 19 |
Seeing the copies
This small class prints whenever it's copied, which makes the difference visible:
#include <iostream> #include <string> #include <utility> struct Tracker { std::string name; explicit Tracker(std::string n) : name(std::move(n)) {} Tracker(const Tracker& other) : name(other.name) { std::cout << " (copied " << name << ")\n"; } }; void by_value(Tracker t) { std::cout << "by value: " << t.name << '\n'; } void by_const_ref(const Tracker& t) { std::cout << "by const&: " << t.name << '\n'; } void by_ref(Tracker& t) { t.name += "!"; } int main() { Tracker big{"report"}; by_value(big); by_const_ref(big); by_ref(big); std::cout << "after by_ref: " << big.name << '\n'; }
(copied report) by value: report by const&: report after by_ref: report!
Only the by-value call made a copy. Classes and constructors are explained in lessons 31–32; for now, watch the "copied" line.
Move semantics in brief
Copying a long string or a big vector duplicates all its data. Moving transfers the data instead — the destination takes over the source's memory, leaving the source valid but empty (or otherwise unspecified). std::move says "I'm done with this; its contents may be taken":
#include <iostream> #include <string> #include <utility> #include <vector> int main() { std::vector<std::string> names; std::string name = "a fairly long name that would be expensive to copy"; names.push_back(name); // copies: name still holds its text names.push_back(std::move(name)); // moves: the text is transferred std::cout << names.size() << " names, original now has " << name.size() << " characters\n"; }
2 names, original now has 0 characters
After a move, only assign a new value to the moved-from object or let it be destroyed — don't rely on what it contains. Lesson 35 explains how classes support moving.
Taking a parameter "by value and move"
When a function stores its argument (for example a setter or a constructor), take it by value and move it into place. Callers passing a temporary then pay for no copy at all, and callers passing a variable pay for exactly one:
#include <iostream> #include <string> #include <utility> class Contact { public: void set_email(std::string email) { email_ = std::move(email); } const std::string& email() const { return email_; } private: std::string email_; }; int main() { Contact c; c.set_email("sam@example.com"); // temporary: moved all the way, no copy std::string backup = "sam.rivera@example.org"; c.set_email(backup); // variable: copied once into the parameter std::cout << c.email() << " / " << backup << '\n'; }
sam.rivera@example.org / sam.rivera@example.org
Returning objects is cheap
Returning a local object by value doesn't copy it: the compiler constructs it directly in the caller's variable (copy elision), or moves it. So write functions that return results by value — don't use output parameters just to "avoid a copy":
#include <iostream> #include <vector> std::vector<int> squares(int n) { std::vector<int> result; for (int i = 1; i <= n; ++i) result.push_back(i * i); return result; // no copy of the vector's contents } int main() { auto s = squares(6); for (int x : s) std::cout << x << ' '; std::cout << '\n'; }
1 4 9 16 25 36
Don't return references to locals
Returning by value is safe; returning a reference or pointer to a local variable is a dangling-reference bug (lesson 13). When in doubt, return by value.
Try it yourself
Write a function that takes a const reference to a list of words and returns a new vector containing only the long ones.
elephant hippopotamus