Deleted Functions
/ 4 min read
Table of Contents
The = delete syntax in C++11 lets you explicitly delete functions, giving much better error messages than the old private declaration trick. It’s cleaner and more expressive.
What Deleted Functions Actually Do
= delete tells the compiler to reject any attempts to call a function:
class NonCopyable {public: NonCopyable() = default;
// Explicitly delete copy operations NonCopyable(const NonCopyable&) = delete; NonCopyable& operator=(const NonCopyable&) = delete;};
NonCopyable obj;NonCopyable copy = obj; // Clear error: function is deletedBefore C++11, you’d make these private and not implement them - much more confusing.
Why This Is Better
The old way was hacky and gave poor error messages:
// Old C++98 wayclass NonCopyable {private: NonCopyable(const NonCopyable&); // Declared but not implemented NonCopyable& operator=(const NonCopyable&); // Hope nobody calls these!};If someone accidentally tried to copy, they’d get a linker error instead of a clear compile error.
When I Actually Use Deleted Functions
Most of the time, I use = delete for:
- Preventing copying: For RAII classes that manage unique resources:
class FileHandle { FILE* file_;
public: explicit FileHandle(const char* filename) : file_(fopen(filename, "r")) {}
~FileHandle() { if (file_) fclose(file_); }
// Can't copy file handles FileHandle(const FileHandle&) = delete; FileHandle& operator=(const FileHandle&) = delete;
// But moving is fine FileHandle(FileHandle&& other) noexcept : file_(other.file_) { other.file_ = nullptr; }};- Preventing unwanted conversions: Avoiding implicit conversions that don’t make sense:
class UserId { int id_;
public: explicit UserId(int id) : id_(id) {}
// Don't allow accidental conversion from other types UserId(double) = delete; // No floating point user IDs UserId(const char*) = delete; // No string user IDs
int value() const { return id_; }};
UserId user1(42); // OKUserId user2(3.14); // Error: deleted functionUserId user3("admin"); // Error: deleted function- Template specializations: Preventing certain template instantiations:
template<typename T>void process_data(T data) { // Generic implementation}
// Delete specialization for dangerous typestemplate<>void process_data<char*>(char*) = delete; // Use std::string instead
template<>void process_data<void*>(void*) = delete; // Too unsafe- Overload resolution control: Controlling which overloads are available:
class Logger {public: void log(const std::string& message); void log(int level, const std::string& message);
// Don't allow logging raw pointers accidentally void log(const char*) = delete; // Force conversion to string void log(void*) = delete; // Probably a mistake};
Logger logger;logger.log("Hello"); // Error - use std::stringlogger.log(std::string("Hi")); // OKDeleted Special Member Functions
The rule of five becomes clearer with deleted functions:
class Resource { void* data_;
public: Resource(); ~Resource();
// Either implement all copy/move operations or delete them Resource(const Resource&) = delete; Resource& operator=(const Resource&) = delete; Resource(Resource&&) = delete; Resource& operator=(Resource&&) = delete;};This makes intent crystal clear - this class doesn’t support any copying or moving.
Real World Example
Here’s how I use deleted functions in a thread-safe singleton:
class DatabaseConnection {private: static std::unique_ptr<DatabaseConnection> instance_; static std::mutex mutex_;
// Private constructor DatabaseConnection() = default;
public: static DatabaseConnection& get_instance() { std::lock_guard<std::mutex> lock(mutex_); if (!instance_) { instance_ = std::unique_ptr<DatabaseConnection>(new DatabaseConnection()); } return *instance_; }
// Delete all copy and move operations DatabaseConnection(const DatabaseConnection&) = delete; DatabaseConnection& operator=(const DatabaseConnection&) = delete; DatabaseConnection(DatabaseConnection&&) = delete; DatabaseConnection& operator=(DatabaseConnection&&) = delete;
void execute_query(const std::string& sql) { // Implementation... }};Deleted vs Private
Deleted functions participate in overload resolution, private functions don’t:
class Test {public: void func(int x); void func(double x) = delete;
private: void func(char x);};
Test t;t.func(42); // Calls func(int)t.func(3.14); // Error: deleted functiont.func('a'); // Error: private functionThe deleted function is considered during overload resolution and then rejected. The private function isn’t considered at all.
Template Function Deletion
You can delete specific template instantiations:
template<typename T>void dangerous_operation(T* ptr) { // Generic pointer operation}
// Delete for specific dangerous typestemplate<>void dangerous_operation<void>(void*) = delete;
template<>void dangerous_operation<const char>(const char*) = delete;The Pattern I Follow
I use = delete to be explicit about what operations are not supported:
// For RAII classes - usually delete copy, allow moveclass UniqueResource {public: UniqueResource(UniqueResource&&) = default; UniqueResource& operator=(UniqueResource&&) = default;
UniqueResource(const UniqueResource&) = delete; UniqueResource& operator=(const UniqueResource&) = delete;};
// For value types - usually allow everything or delete everythingclass ImmutableValue {public: ImmutableValue(const ImmutableValue&) = default; ImmutableValue& operator=(const ImmutableValue&) = delete; // Immutable!};Deleted functions make code more self-documenting and give better error messages. It’s a small feature, but it does a lot to express design intent clearly.