Languages
C++
Semantics & Pointers

Move Semantics and Smart Pointers in Modern C++

#include <memory>
#include <vector>
 
class Resource {
    int* data;
public:
    Resource() : data(new int[100]) {}
    ~Resource() { delete[] data; }
 
    // Move constructor
    Resource(Resource&& other) noexcept : data(other.data) {
        other.data = nullptr;  // Prevent double deletion
    }
 
    // Move assignment
    Resource& operator=(Resource&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            other.data = nullptr;
        }
        return *this;
    }
 
    // Disable copying
    Resource(const Resource&) = delete;
    Resource& operator=(const Resource&) = delete;
};
 
void process_resource(std::unique_ptr<Resource> res) {
    // Exclusive ownership transferred
}
 
int main() {
    auto ptr = std::make_unique<Resource>();  // Smart pointer
    std::vector<Resource> resources;
    resources.push_back(Resource());  // Move semantics in action
    process_resource(std::move(ptr));  // Explicit ownership transfer
}

Move Semantics: Beyond Copying

The Problem Move Solves

Traditional C++ copies objects unnecessarily:

std::vector<std::string> createStrings() {
    std::vector<std::string> vec{"large", "data"};
    return vec;  // Pre-C++11: Expensive copy
}               // Post-C++11: Efficient move

Key Components

  1. Rvalue References (&&)
    void foo(std::string&& s);  // Accepts temporary objects
  2. Move Constructor
    MyClass(MyClass&& other) noexcept;
  3. Move Assignment Operator
    MyClass& operator=(MyClass&& other) noexcept;

The std::move Utility

  • Doesn't actually move anything
  • Casts an lvalue to rvalue reference
  • Signals "this object can be moved from"
std::string s1 = "Hello";
std::string s2 = std::move(s1);  // s1 now in valid but unspecified state

Smart Pointers: Automatic Memory Management

Comparison of Smart Pointers

TypeOwnershipThread-SafeUse Case
std::unique_ptrUniqueNoExclusive ownership
std::shared_ptrSharedYes (ref count)Shared ownership
std::weak_ptrWeakYesBreak reference cycles
std::auto_ptrUniqueNoDeprecated (use unique_ptr)

std::unique_ptr

auto ptr = std::make_unique<MyClass>();  // Preferred creation
ptr->doSomething();  // Normal pointer syntax
 
// Ownership transfer
std::unique_ptr<MyClass> newOwner = std::move(ptr);

std::shared_ptr

auto shared = std::make_shared<MyClass>();
auto shared2 = shared;  // Reference count increases
 
// With custom deleter
std::shared_ptr<FILE> file(
    fopen("data.txt", "r"),
    [](FILE* f) { if(f) fclose(f); }
);

std::weak_ptr

auto shared = std::make_shared<MyClass>();
std::weak_ptr<MyClass> weak = shared;
 
if (auto temp = weak.lock()) {  // Convert to shared_ptr
    temp->use();  // Safe to use
}

Rule of Five (Since C++11)

For resource-managing classes, define or delete:

  1. Destructor
  2. Copy constructor
  3. Copy assignment
  4. Move constructor
  5. Move assignment
class ManagedResource {
public:
    // Constructor/destructor
    ManagedResource();
    ~ManagedResource();
 
    // Copy operations (deleted)
    ManagedResource(const ManagedResource&) = delete;
    ManagedResource& operator=(const ManagedResource&) = delete;
 
    // Move operations
    ManagedResource(ManagedResource&&) noexcept;
    ManagedResource& operator=(ManagedResource&&) noexcept;
};

Performance Impact

Where Move Semantics Help

  1. Return value optimization (RVO)
    std::vector<int> createVector() {
        return std::vector<int>{1,2,3};  // No copy/move happens (RVO)
    }
  2. Container operations
    std::vector<std::string> vec;
    vec.push_back(std::string("test"));  // Moves instead of copies
  3. Algorithm efficiency
    std::sort(vec.begin(), vec.end());  // Swaps elements via moves

Common Pitfalls

  1. Moving from objects still needed

    auto s1 = std::string("text");
    auto s2 = std::move(s1);
    // s1 is now empty - accessing it is legal but often unintended
  2. Not marking moves as noexcept

    • Many standard library operations require noexcept moves
  3. Mixing smart pointers with raw pointers

    MyClass* raw = shared_ptr.get();
    delete raw;  // Disaster - double deletion
  4. Circular references with shared_ptr

    struct Node {
        std::shared_ptr<Node> next;  // Use weak_ptr instead for cycles
    };

Best Practices

  1. Prefer make_shared/make_unique

    • Exception safety
    • Single allocation (for shared_ptr)
  2. Use const& for "in" parameters

    • For non-movable, non-copyable types
    • When you don't need to store the parameter
  3. Return by value

    • Let compiler optimize with RVO/move
  4. Mark move operations as noexcept

    • Enables optimizations in standard containers
  5. Follow RAII principles

    • Acquire resources in constructors
    • Release in destructors

Advanced Topics

Perfect Forwarding

template<typename T>
void wrapper(T&& arg) {
    process(std::forward<T>(arg));  // Preserves value category
}

Custom Deleters

std::unique_ptr<FILE, decltype(&fclose)>
    file(fopen("data.txt", "r"), &fclose);

Type Erasure with std::shared_ptr

std::shared_ptr<void> eraseType =
    std::make_shared<std::string>("hidden");

Further Reading

Last updated on April 10, 2025