Creating content is not easy, if convenient, please click to follow, thank you.
Click on “C++ Players, please get ready” below, select “Follow/Pin/Star the public account” for valuable content and benefits, delivered to you first! Recently, some friends said they did not receive the articles pushed on the same day, which is due to WeChat changing the push mechanism, causing those who did not star the public account to miss the articles pushed on that day, and unable to receive some practical knowledge and information. Therefore, it is recommended that everyone star ⭐️, so you can receive the push at the first time in the future.

1. The Dilemma Before C++11
In software engineering, the clearer the intent of the code, the higher its robustness and maintainability. However, before the C++11 standard appeared, C++ developers often faced a seemingly simple yet tricky problem: How to explicitly prohibit certain unwanted function calls?
The most typical example is prohibiting an object of a class from being copied. In the C++98/03 era, if a class did not explicitly declare a copy constructor and a copy assignment operator, the compiler would automatically generate default versions for them. This is convenient in many cases, but for classes managing exclusive resources (such as file handles, network sockets, or the predecessor of unique_ptr, <span>std::auto_ptr</span>), the default shallow copy behavior is disastrous.
To solve this problem, C++03 developers adopted a conventional trick:
// C++03 style: prohibit copying
class NonCopyable {
private:
NonCopyable(const NonCopyable&);
NonCopyable& operator=(const NonCopyable&);
public:
NonCopyable() {}
};
class MyResourceHolder :private NonCopyable {
// ...
};
int main() {
MyResourceHolder r1;
// MyResourceHolder r2 = r1; // compilation error
// MyResourceHolder r3;
// r3 = r1; // compilation error
return 0;
}
This method cleverly utilizes the compiler’s access control and linkage rules to prevent copying by declaring the copy constructor and copy assignment operator as <span>private</span> and not providing implementations. Any code attempting to copy an object outside the class will fail to compile due to access to <span>private</span> members.
However, this approach has several obvious drawbacks:
- Unclear Intent:
<span>private:</span>is an access control specifier primarily intended for encapsulation. Using it to “prohibit” function calls is an indirect, non-self-documenting trick. Junior developers may not understand its true intent at a glance. - Unfriendly Error Messages: When external code attempts to copy, the compiler reports “cannot access private member”. This error message does not directly indicate “this object is not allowed to be copied”. Worse, if the copy is attempted within a member function or friend function, the compile-time access check will pass, but it will ultimately fail at the linking stage due to the missing function definition, resulting in a confusing “undefined reference” linking error.
- Inability to Fully Prevent Calls: As mentioned above, code within the class’s member functions and friend functions can legally call these
<span>private</span>function declarations, which delays the error until the linking stage, postponing the discovery of the problem.
The C++ community needed a more direct, clearer, and powerful mechanism to express the design intent of “prohibition”.
2. C++11’s Answer: Explicitly Deleted Functions (<span>= delete</span>)
The C++11 standard provides an elegant and powerful answer: **explicitly deleted functions (explicitly deleted functions)**.
The syntax is very simple, just add <span>= delete;</span> at the end of the function declaration.
// C++11 and later
ReturnType functionName(Parameters) = delete;
<span>= delete</span> is fundamentally about providing a clear, explicit, and compile-time enforced mechanism to disable any function, including member functions, non-member functions, regular functions, and template functions.
When a function is marked as <span>= delete</span>, any attempt to use (call) that function will result in an intuitive compile-time error. This completely resolves all the shortcomings of the C++03 privatization scheme.
3. Core Application Scenarios of <span>= delete</span>
<span>= delete</span> has applications far beyond just prohibiting copying. It provides C++ developers with a precise control mechanism akin to a “surgical knife” for code behavior.
3.1. Prohibiting Object Copying – The Most Classic Use Case
This is the most well-known use of <span>= delete</span>. Let’s rewrite <span>NonCopyable</span> in a modern C++ way:
#include <iostream>
class ResourceHolder {
private:
int* data;
public:
ResourceHolder() : data(new int(0)) {
std::cout << "Resource acquired." << std::endl;
}
~ResourceHolder() {
delete data;
std::cout << "Resource released." << std::endl;
}
// Explicitly delete copy constructor
ResourceHolder(const ResourceHolder&) = delete;
// Explicitly delete copy assignment operator
ResourceHolder& operator=(const ResourceHolder&) = delete;
};
int main() {
ResourceHolder r1;
// ResourceHolder r2 = r1; // attempt to copy construct
// ResourceHolder r3;
// r3 = r1; // attempt to copy assign
return 0;
}
Compilation and Output: When you try to compile the above code, you will receive a very clear error message (using GCC as an example):
error: use of deleted function ‘ResourceHolder::ResourceHolder(const ResourceHolder&)’
note: ‘ResourceHolder::ResourceHolder(const ResourceHolder&)’ is implicitly deleted because the default definition would be ill-formed:
This error directly points out the problem: “use of deleted function”. Compared to C++03’s “cannot access private member”, this information is much friendlier for debugging.
3.2. Prohibiting Object Movement
Similar to copying, sometimes we also need to prohibit the movement of objects. A typical scenario is when a class encapsulates a resource handle from a lower-level C library, and the address of this handle cannot change, or the class itself is a fixed observer point in an observer pattern.
#include <iostream>
// Assume this is a non-movable lower-level resource
struct NonMovableHandle {
int handle_id;
};
class ResourceManager {
private:
NonMovableHandle handle;
public:
ResourceManager(int id) : handle({id}) {}
// Prohibit move construction
ResourceManager(ResourceManager&&) = delete;
// Prohibit move assignment
ResourceManager& operator=(ResourceManager&&) = delete;
// Copy operations are also deleted, as it manages a unique resource
ResourceManager(const ResourceManager&) = delete;
ResourceManager& operator=(const ResourceManager&) = delete;
void print() const {
std::cout << "Managing handle: " << handle.handle_id << std::endl;
}
};
int main() {
ResourceManager r1(123);
r1.print();
// ResourceManager r2 = std::move(r1); // compilation error: use of deleted function
return 0;
}
By deleting the move operations, we statically ensure that once a <span>ResourceManager</span> instance is created, the resource it manages will not be “stolen”.
3.3. Preventing Unwanted Function Overloading and Type Conversion
This is a key part where <span>= delete</span> demonstrates its depth and power. It can be used not only for special member functions but also for any regular function to prevent dangerous implicit type conversions and unwanted function overloading.
Example A: Avoiding Dangerous Implicit Type Conversions
Imagine a function designed to only handle high-precision <span>double</span> types. If the compiler is allowed to automatically convert <span>int</span> or <span>float</span> to <span>double</span>, it may obscure precision loss issues at the call site.
#include <iostream>
void processValue(double value) {
std::cout << "Processing a double: " << value << std::endl;
}
// Explicitly prohibit implicit conversion from int to double
void processValue(int value) = delete;
// Explicitly prohibit implicit conversion from float to double
void processValue(float value) = delete;
// Also prohibit other unwanted types, such as bool
void processValue(bool value) = delete;
int main() {
processValue(3.14); // OK
// processValue(42); // compilation error: use of deleted function
// processValue(true); // compilation error: use of deleted function
// If you want to pass an integer, you must explicitly convert, indicating the developer is aware of this
processValue(static_cast<double>(42)); // OK
return 0;
}
Another example is to force the caller to explicitly manage memory ownership. Suppose a function accepts a string, by deleting the <span>const char*</span> overload, the API user is forced to create a <span>std::string</span> object, making memory management responsibilities clearer.
#include <string>
#include <iostream>
void printMessage(const std::string& msg) {
std::cout << msg << std::endl;
}
// Prohibit passing C-style string literals, forcing the caller to create std::string
void printMessage(const char*) = delete;
int main() {
printMessage(std::string("Hello")); // OK
// printMessage("Hello"); // compilation error: use of deleted function
return 0;
}
Example B: Prohibiting Specific Parameter Types
<span>std::nullptr_t</span> was introduced to solve the overloading ambiguity caused by the <span>NULL</span> macro (which is often <span>0</span> or <span>(void*)0</span>). We can use <span>= delete</span> to further enhance pointer handling safety, preventing the passing of <span>NULL</span> or <span>nullptr</span>.
#include <cstddef> // For std::nullptr_t
void processRawPointer(void* ptr) {
// ... do something with a valid pointer
}
// Explicitly prohibit passing nullptr
void processRawPointer(std::nullptr_t) = delete;
// On 64-bit systems, NULL may be defined as 0L, leading to overloading to long
// Explicitly prohibit integer types to prevent misuse of NULL
void processRawPointer(int) = delete;
void processRawPointer(long) = delete;
int main() {
int x = 10;
processRawPointer(&x); // OK
// processRawPointer(nullptr); // compilation error
// processRawPointer(NULL); // compilation error (usually matches int or long deleted version)
return 0;
}
3.4. Precise Control Over Object Creation
Example A: Prohibiting Object Creation on the Heap
If you want to design an object that can only be created on the stack or as a member variable (for example, a lightweight RAII wrapper that does not want complex lifecycle management), you can delete its <span>operator new</span>.
#include <cstddef>
class StackOnly {
public:
StackOnly() = default;
// Delete operator new, preventing new StackOnly()
void* operator new(std::size_t) = delete;
// It is also best to delete new[]
void* operator new[](std::size_t) = delete;
};
int main() {
StackOnly s1; // OK, created on the stack
// StackOnly* p = new StackOnly(); // compilation error: use of deleted function
return 0;
}
Example B: Prohibiting Object Creation on the Stack
This is a more advanced use case, often used to implement a pattern where objects must be managed through factory functions or smart pointers. There are various ways to achieve this, one of which is to delete the destructor.
If a class’s destructor is deleted, then any instance of that class cannot be destroyed. Since objects created on the stack (automatic storage duration) must be automatically destroyed when they leave scope, deleting the destructor will directly prevent objects from being created on the stack.
#include <iostream>
class HeapOnly {
public:
HeapOnly() { std::cout << "HeapOnly created.\n"; }
// Provide a public destroy method
void destroy() {
delete this;
}
private:
// Making the destructor private is a common practice
// ~HeapOnly() { std::cout << "HeapOnly destroyed.\n"; }
public:
// C++11 provides a clearer way: delete the destructor
// Note: this is a stronger restriction, it also prohibits delete ptr;
// This is why we usually need a destroy() member function to call delete this;
// However, directly deleting the destructor will also call the destructor on delete this; causing compilation failure.
// Therefore, a more practical pattern is a private destructor + public destroy() method.
// To illustrate the capability of = delete, we show its effect.
~HeapOnly() = delete;
};
// Compiling this code will fail due to ~HeapOnly() = delete;
// because the main function ends, s cannot be destroyed.
/*
int main() {
// HeapOnly s; // compilation error: trying to reference a deleted function
return 0;
}
*/
Note: Directly deleting the destructor is a very strong restriction, it will cause any place that needs to destroy the object to fail to compile, including <span>delete ptr</span>. Therefore, in practice, declaring the destructor as <span>private</span> and providing a public <span>destroy()</span> method is a more common and flexible pattern for implementing “can only be created on the heap”. The demonstration of <span>= delete</span> here is mainly to illustrate its syntactic capability.
3.5. Restricting Template Instantiation
<span>= delete</span> is also extremely useful in template metaprogramming. We can provide a <span>= delete</span> template specialization version for specific types that we do not want to be used, thus preventing erroneous template instantiation at compile time.
#include <string>
#include <iostream>
// A template function that only wants to handle non-pointer types
template <typename T>
void process(T value) {
std::cout << "Processing value: " << value << std::endl;
}
// Specialize and delete the void* version to prevent processing untyped pointers
template <>
void process<void*>(void* value) = delete;
// Specialize and delete the const char* version to force the use of std::string
template <>
void process<const char*>(const char* value) = delete;
int main() {
process(123); // OK
process(std::string("hello")); // OK
int x = 10;
int* p = &x;
// process(p); // OK, T deduced as int*
void* vp = p;
// process(vp); // compilation error: use of deleted function
// process("raw string"); // compilation error: use of deleted function
return 0;
}
This allows API designers to sculpt their interfaces, precisely eliminating types that do not conform to design intent, making the API safer and more robust.
4. Comparison of <span>= delete</span> vs. <span>private</span> Scheme: Comprehensive Comparison
| Comparison Dimension | <span>= delete</span> (C++11 and later) |
<span>private</span> Declaration (C++03 Style) |
|---|---|---|
| Clarity of Intent | Extremely high, code is documentation, directly expressing “prohibit use” | Indirect, requires developers to understand this as a conventional trick |
| Error Reporting | Compile-time, clear information: “use of deleted function” | Compile-time access error (external calls) or linking error (member/friend calls) |
| Scope of Applicability | Any function (member, non-member, template, operator, etc.) | Limited to member functions |
| Error Detection Timing | Earlier, can be discovered at overload resolution stage | Later, at access check stage, may even be at linking stage |
| Impact on Friends | Friends also cannot call, rules are universal | Friends can call declarations, leading to linking errors |
5. Conclusion
<span>= delete</span> is a seemingly minor yet profoundly significant feature introduced in C++11. It is not just syntactic sugar, but a reflection of the evolution of C++ language design philosophy—from relying on conventions and tricks to providing native, explicit language features.
Through <span>= delete</span>, we gain three core values:
- **Clarity**: The code directly reflects the designer’s intent, making it easier to understand and maintain.
- **Safety**: Potential runtime errors or linking errors are transformed into clear compile-time errors, achieving the principle of “let errors happen early”.
- **Precision Control**: Allows API designers to define the legal usage scope of their interfaces with surgical precision, preventing misuse.
In modern C++ programming and API design, <span>= delete</span> has become an indispensable tool. It encourages developers to think more deeply about class behavior and interface constraints. Therefore, we can say: **<span>= delete</span> is not just a syntax, but a philosophy that enhances code design intent and robustness.**
This article is a professional and reliable content formed after strict review of relevant authoritative literature and materials. All data in the full text can be traced back. It is particularly stated: the data and materials have been authorized. The content of this article does not involve any biased views, describing the facts objectively with a neutral attitude.