OSC++.027: std::exchange — Replace a Value and Return the Old One

African American C++ developer studying resource transfer and move semantics on dual monitors

std::exchange replaces an object’s value and returns its previous value in one expression. It is especially useful when implementing move operations, transferring handles, resetting state, or writing code where “take the old value, leave a known replacement behind” is the exact intent.

This lesson follows OSC++.026: Custom swap and ADL, OSC++.025: Copy-and-Swap and Exception-Safe Assignment, and OSC++.024: Rule of Five and Rule of Zero. Those lessons established ownership, move operations, assignment, and swapping. std::exchange gives you a compact way to transfer a value while immediately resetting the source.

Mike Shah introduces std::move and move semantics, which provide the ownership-transfer foundation needed to understand why std::exchange is useful in move constructors.

Learning Objectives

  • Explain exactly what std::exchange does.
  • Distinguish std::exchange from std::move and std::swap.
  • Use std::exchange in move constructors safely.
  • Recognize the resource-leak risk in careless move assignment.
  • Explain why the replacement value may have a different but assignable type.
  • Understand the C++14, C++20, and C++23 behavior changes.
  • Use the feature-test macro when version-sensitive code requires it.

What std::exchange Does

std::exchange is declared in <utility>. Its conceptual form is std::exchange(obj, new_value). The function moves the old value out of obj, assigns new_value into obj, and returns the old value.

The current cppreference entry for std::exchange shows the essential behavior as three steps: create old_value from std::move(obj), assign the forwarded replacement into obj, then return old_value.

Simple example: int old = std::exchange(value, 0);. If value was 42, then after the expression old is 42 and value is 0.

std::exchange Is Not std::move

std::move does not move anything by itself. It converts an expression into an xvalue so that move-aware overloads can be selected. The current cppreference std::move reference describes it as a cast that indicates an object may be moved from.

std::exchange performs an actual two-part state transition: it obtains the old value and then assigns a replacement value into the source object. That makes it useful when you need the moved-from object to immediately contain a specific known state such as nullptr, 0, false, or another sentinel.

A C++ community discussion directly compares std::move and std::exchange and reinforces the key distinction: std::move changes value category, while std::exchange replaces state and returns the old state.

std::exchange Is Not std::swap

std::swap(a, b) exchanges the values of two existing objects. After the call, a contains the old value of b and b contains the old value of a.

std::exchange(a, replacement) is asymmetric. It replaces a with replacement and returns the previous value of a. The replacement expression does not receive the old value.

This is why std::exchange is often clearer for “take and reset” operations, while swap is better when two complete object states should trade places.

A Move Constructor Example

Suppose a class owns a raw handle stored in handle_, and the empty state is represented by nullptr. A move constructor can write the transfer directly as Widget(Widget&& other) noexcept : handle_(std::exchange(other.handle_, nullptr)) {}.

Read that expression literally: take the old pointer out of other.handle_, put nullptr into other.handle_, and use the returned old pointer to initialize the new object’s handle_.

The source object remains valid for destruction because it no longer claims ownership of the transferred handle. This is one of the most recognizable uses of std::exchange in resource-owning move constructors.

Fred Overflow reviews move construction and resource ownership transfer, the exact context where resetting a moved-from handle with std::exchange becomes useful.

Why the Moved-From State Matters

For standard-library types, moved-from objects are generally left in a valid but unspecified state unless otherwise documented. Your own resource-owning class must also remain safe to destroy and assign after a move.

Using std::exchange can make that state explicit. A pointer becomes nullptr. A file descriptor can become an invalid sentinel such as -1. A size or count can become 0. The class invariant becomes easier to read because the transfer and reset happen together.

Be Careful in Move Assignment

A move constructor starts with a new object that does not yet own an old resource. Move assignment is different: the destination object may already own something that must be released first.

For example, writing handle_ = std::exchange(other.handle_, nullptr); can leak the resource previously stored in handle_ if assigning the new handle simply overwrites the old one. The cppreference notes for std::exchange explicitly warn about this kind of opaque-resource-handle case.

A correct move-assignment operator must first preserve or release the destination’s existing resource according to the class’s ownership rules. Depending on the design, that may mean cleanup before assignment, a temporary owner, a custom swap, copy-and-swap, or Rule-of-Zero member types that already manage the resource safely.

Different Replacement Types Are Allowed

The template has two types, conceptually T for the object and U for the replacement value. U defaults to T, but it may be a different type as long as the assignment is valid.

This flexibility is intentional. The original WG21 proposal, N3668 — exchange() utility function, revision 3, explains that the second type allows converting assignment and avoids unnecessary temporary objects.

A focused C++ discussion walks through why std::exchange uses a second template parameter and a forwarding reference for the replacement value.

Version History

  • C++14: std::exchange entered the standard library.
  • C++20: it became constexpr.
  • C++23: its exception specification became conditionally noexcept based on whether moving the old value and assigning the replacement can throw.
  • Feature-test macro: __cpp_lib_exchange_function has value 201304L.

The WG21 document list records N3668 as adopted in 2013 for the library work that became part of C++14. The modern standard-library reference also documents the later constexpr and conditional-noexcept changes.

Readable State Transitions

std::exchange is useful outside move constructors whenever a program needs to consume the old state while setting the next state. Examples include replacing a callback pointer, resetting a “first item” flag, taking a numeric counter before clearing it, or updating state-machine variables while preserving the prior state for one expression.

Do not use it merely because it is compact. A normal assignment is clearer when the old value is irrelevant. std::exchange earns its place when both parts of the operation matter: you need the old value and you need the source replaced immediately.

Troubleshooting and Review Checklist

  1. Ask whether the code actually needs the old value.
  2. Confirm the replacement value represents a valid state for the source object.
  3. If the object owns a resource, determine who owns the old resource after the exchange.
  4. In move assignment, confirm the destination’s current resource is released safely before overwrite.
  5. Check whether Rule of Zero can eliminate the custom move code entirely.
  6. Check whether swap communicates the intent better when two full states are trading places.
  7. Check exception behavior if the operation participates in a noexcept move path.
  8. Compile with the intended language standard because availability and exception specifications depend on the C++ version.

Practice Exercise

Create a small class that owns a dynamically allocated integer through a raw pointer only for educational purposes. Give the class a destructor and a move constructor. In the move constructor, initialize the destination pointer with std::exchange(other.ptr_, nullptr).

Then construct one object, move it into another object, and verify that the source pointer becomes nullptr. Next, attempt to design move assignment. Before writing the assignment line, identify what must happen to any resource already owned by the destination. Finally, replace the raw pointer with std::unique_ptr and compare how much custom lifecycle code disappears under the Rule of Zero.

Knowledge Check

  1. What value does std::exchange(obj, new_value) return?
  2. What value remains in obj afterward?
  3. How is std::exchange different from std::move?
  4. How is it different from std::swap?
  5. Why is std::exchange(other.ptr_, nullptr) useful in a move constructor?
  6. Why can the same pattern be dangerous in move assignment?
  7. In which C++ version was std::exchange introduced?
  8. What is the feature-test macro value?

Answers

  1. The old value of obj.
  2. The assigned replacement value.
  3. std::move changes value category; std::exchange moves out the old value, assigns a replacement, and returns the old value.
  4. swap trades the values of two objects; exchange replaces one object and returns its old value.
  5. It transfers the pointer to the destination and immediately resets the source to a non-owning null state.
  6. The destination may already own a resource that would be leaked if its handle is overwritten without cleanup.
  7. C++14.
  8. 201304L.

Key Takeaway

std::exchange means “give me the old value and leave this replacement behind.” It is compact, expressive, and particularly effective for move constructors and explicit state transitions—but only when ownership and cleanup rules remain correct.

Previous Related Lessons

Primary references: WG21 N3668 — exchange() utility function, revision 3, cppreference — std::exchange, and cppreference — std::move.

BitcoinVersus.Tech Editor’s Note: The examples intentionally expose manual ownership concepts for instruction. In production C++, prefer RAII and Rule-of-Zero designs such as std::unique_ptr whenever they correctly model the resource.

Support independent technology education: Bitcoin donations help fund BitcoinVersus.Tech research and publishing.

BitcoinVersus.Tech provides technical education and analysis for informational purposes only.

Leave a Reply