CERT.EXPR.MOVED_FROM.USE
Do not use a moved-from object
The CERT.EXPR.MOVED_FROM.USE checker detects use-after-move defects in C++ programs (C++11 and later). When std::move is applied to an object and the result is consumed (passed to a move constructor or move assignment operator), the source object enters a valid but unspecified state. Any subsequent use of that moved-from object, such as reading its value, invoking a non-reinitializing member function, or passing it to a consuming function, relies on an unspecified value and therefore produces unspecified or undefined behavior.
Vulnerability and risk
The C++ standard guarantees only that a moved-from object is in a valid but unspecified state — it is safe to destroy or reassign, but its value is unspecified. Accessing a moved-from std::string may yield an empty string or any other value, silently corrupting output. Accessing a moved-from std::vector is typically empty, causing range loops and index-based accesses to silently process no elements or go out of bounds. Passing a moved-from object to a function that expects a valid value silently propagates the unspecified state further into the call chain. In multi-step pipelines, the defect is often far from the move site, making it difficult to diagnose without a tool.
Mitigation and prevention
Do not read or call non-trivial methods on an object after passing it to std::move. If the variable must be reused, assign a new value to it before the next use; any assignment, including operator=, reinitializes the moved-from state. Limit the scope of the variable so that its declaration is immediately followed by the move, leaving no room for accidental subsequent use. Use std::exchange when you need to transfer ownership and reset the source to a known value in one operation.
Vulnerable code example 1: method call on moved-from object
#include <string>
#include <utility>
void f() {
std::string s = "hello";
std::string t = std::move(s); // s enters moved-from state
std::size_t n = s.size(); // CERT.EXPR.MOVED_FROM.USE: 's' used after move
}
After std::move(s) is consumed by the move constructor of t, s is in a moved-from state. Calling s.size() reads an unspecified value. In practice most std::string implementations leave the moved-from string empty, but the standard does not guarantee this.
Vulnerable code example 2: moved-from object passed to free function
#include <string>
#include <utility>
void consume(std::string s);
void g() {
std::string s = "hello";
consume(std::move(s)); // s enters moved-from state
consume(s); // CERT.EXPR.MOVED_FROM.USE: 's' used after move
}
The first call transfers ownership of the string data; the second call copies from the moved-from s, whose value is unspecified.
Vulnerable code example 3: double move of a non-smart-pointer object
#include <string>
#include <utility>
void h() {
std::string s = "hello";
std::string t = std::move(s); // s enters moved-from state
std::string u = std::move(s); // CERT.EXPR.MOVED_FROM.USE: 's' used after move
// u contains an unspecified value — silent logic error
}
Unlike smart pointers (whose moved-from state is always nullptr), std::string and most other standard-library types have an unspecified moved-from value. Passing the moved-from s to a second std::move reads that unspecified value.
Fixed code example 1: do not use the object after the move
#include <string>
#include <utility>
void consume(std::string s);
void f() {
std::string s = "hello";
consume(std::move(s)); // s is not used after this point — compliant
}
Fixed code example 2: reinitialize before reuse
#include <string>
#include <utility>
void f() {
std::string s = "hello";
std::string t = std::move(s); // s enters moved-from state
s = "world"; // reinitialize: assignment restores s
std::size_t n = s.size(); // OK: s has a well-defined value again
}