C++ copy and move
Published 2026-08-03I'm currently reading A Tour of C++ by Bjarne Stroustrup. One thing that stood out to me was creating classes to wrap resources. Resources include file descriptors, pointers and really anything that needs it's lifetime managed. And by lifetime I mean how long the object stays in scope before its destructor is called. Let's look at this example:
#include <iostream>
class Foo {
public:
Foo() { log("construct"); }
~Foo() { log("destruct"); }
void log(std::string msg) {
std::cout << "[" << this << "]: " << msg << std::endl;
}
};
int main() {
Foo foo;
return 0;
}
$ g++ -std=c++20 main.cpp && ./a.out
[0x7ffc80aee537]: construct
[0x7ffc80aee537]: destruct
The variable foo is constructed on the stack and then when we leave the scope of main, it's destructor is automatically called. Let's make it slightly more complex. What if foo is going to be used in another thread? We'll define a copy and move constructor.
Foo(const Foo& other) { log("copy"); }
Foo(Foo&& other) { log("move"); }
int main() {
Foo f;
std::thread t([f]{
f.log("in thread");
});
t.join();
return 0;
}
$ g++ -std=c++20 main.cpp && ./a.out
[0x16f3fb06b]: construct
[0x16f3fb05f]: copy
[0x600000a0c058]: move
[0x16f3fb05f]: destruct
[0x600000a0c058]: in thread
[0x600000a0c058]: destruct
[0x16f3fb06b]: destruct
Since foo is copied then moved into the thread, it raises questions about the lifetime of our object. Foo is simple enough that its ok if there are multiple copies. Things get more complicated if our class contains a resource that shouldn't be copied. For example, I've been playing around with epoll, which is a Linux API for multiplexing I/O.
For demonstration purposes we can show how to use C++ and RAII as a way to manage the lifetime of an epoll file descriptor. C++, like most languages, uses constructors. What was new to me was copy and move semantics. And these are really all about the ownership model of your object; whether it can have multiple owners at a time or single.
It would be a really bad idea if we shared that file descriptor across multiple threads, so we'll delete the copy constructor. Now, what does it mean to move something in C++. It's really about transferring ownership. So when we define this constructor we need to think about who is taking ownership of the object. Let's see what we can come up with.
class EpollHandle {
int fd = -1;
public:
EpollHandle() {
fd = epoll_create1(0);
if (fd == -1) {
throw std::runtime_error("could not create epoll instance");
}
}
~EpollHandle() {
if (fd == -1) return;
close(fd);
fd = -1;
}
// Shouldn't copy file descriptors
EpollHandle(const EpollHandle&) = delete;
EpollHandle& operator=(const EpollHandle&) = delete;
// But definitely can move
EpollHandle(EpollHandle&& other) noexcept : fd(other.fd) {
std::cout << "move ctor" << std::endl;
other.fd = -1;
}
EpollHandle& operator=(EpollHandle&& other) {
std::cout << "move assignment" << std::endl;
if (this == &other) return *this;
if (fd != -1) close(fd);
fd = other.fd;
other.fd = -1;
return *this;
}
};
So our class is complete. We've made it possible to safely transfer ownership of the object and also elimintated the possibility of creating copies.