Deleted Functions vs Private Members in C++
In modern C++, developers frequently need to restrict access to
specific functions, such as preventing object copying or disallowing
certain type conversions. Historically, this was achieved by declaring a
member function as private and leaving it undefined (or
unexported), whereas C++11 introduced explicitly deleted functions using
the = delete specifier. While both techniques prevent
unauthorized execution, they differ fundamentally in how they interact
with overload resolution, access control, compiler diagnostics, and
internal class privileges.
Overload Resolution and Error Generation
The primary functional difference lies in the sequence of compiler phases: overload resolution occurs before access control checks.
When a function is declared private but left unexported
or undefined:
- The compiler includes the private function in the candidate set during overload resolution.
- If it is the best match, the compiler selects it.
- The compiler then evaluates access control. If the call site lacks access rights, compilation halts with an access violation error.
When a function is marked = delete:
- The function remains a viable candidate in overload resolution.
- If it provides the best match, overload resolution selects it.
- The compiler immediately evaluates the deletion status and produces a compilation error stating that the deleted function cannot be referenced.
This distinction is crucial when preventing unwanted implicit
conversions. For example, if an overloaded method accepts an
int and a deleted overload accepts a double,
passing a double resolves to the deleted overload and fails
immediately. If the double overload were simply private,
external callers would trigger an access error rather than falling back
to int, but within the class or its friends, the function
might still be mistakenly called and lead to a linker error.
Member and Friend Access
An unexported private member does not protect against invocation from
within the class itself or from declared friend
entities:
- Private and Undefined: Member functions and friends can still legally attempt to call the private function. Because the function is intentionally left without an export or definition, the failure is deferred to the linker, yielding an "undefined reference" or unresolved external symbol error rather than a direct compilation failure.
- Explicitly Deleted (
= delete): Deletion is absolute. Neither member functions nor friends can call an explicitly deleted function. Any attempt to invoke it results in an immediate compile-time error, ensuring full consistency across the entire codebase.
Diagnostic Clarity and Intent
Compiler diagnostics differ substantially between the two mechanisms:
- Using
= deletecommunicates explicit design intent to the compiler. Diagnostic messages explicitly report that the code attempted to invoke a deleted function, often highlighting the exact line where= deletewas declared. - Private, unexported functions report access violations (e.g., "function is private within this context") for external callers, or missing symbols at link time for internal callers, obscuring the design intent that the operation is fundamentally invalid.
Scope of Application
Deleted functions possess broader functional utility than access specifiers:
- Non-Member Functions: Free functions and
namespace-scope functions can be explicitly deleted (e.g., to prevent
specific argument type conversions), whereas
privateaccess is restricted exclusively to class members. - Function Template Specialization: Specific template instantiations can be deleted to disable problematic types while keeping the primary template enabled for all other types. Private access cannot selectively disable template specializations in this manner.
Impact on Type Traits
Type traits inspect the validity of operations at compile time:
- Classes with explicitly deleted special member functions (such as
copy constructors or copy assignment operators) correctly update type
traits such as
std::is_copy_constructibleandstd::is_copy_assignableto evaluate tofalse. - Private special member functions in older C++ standards often complicated SFINAE contexts and introspection traits, whereas deleted definitions integrate natively with modern template metaprogramming and concept constraints.