Prefer deleted functions to private undefined ones

This item (11) in the chapter 3 focuses on:

  • Why and How to prevent users calling particular functions?
  • C++-98 and C++-11 approach
  • What’s the difference between deleting a function vs declaring a member function private (and not defining them)?


NOTE

These are my notes on Chapter 3, Item 11 of Effective Modern C++ written by Scott Meyers.

Some (or even all) of the text can be similar to what you see in the book, as these are notes: I’ve tried not to be unnecessarily creative with my words. :)


  • When is it required to delete a function/not define a private member function?

Problem:

  • Cases when you don’t want the client to call a particular function.

Solution:

  • Just don’t declare the function

But…doesn’t work always:

Case: Special member functions generated by C++ automatically, discussed later). Examples considered in this blog:

  • Copy Constructor
  • Copy Assignment Operator

C++-98 Approach:

  • Declare these functions private and don’t define them.

  • Example:

    • All istream and ostream objects inherit (possibly) from basic_ios class in the C++ Standard Library.
    • Copying these objects is undersirable.
  • Why is copying objects of istream and ostream undesirable? [Also see this question on stackoverflow]

    • istream object: represents stream of input values.
      • some might have been read before.
      • and some may be read later.
    • If you copy istream object:
      • Will that copy those values which have been read before?
      • Or will also copy values which are to be read later?

Hence, it’s just better to not allow copying istream or ostream objects.

In C++-98:

Reminder (from above): All istream and ostream objects inherit from basic_ios class (possibly) in the C++ standard, and the basic_ios class in C++-98 looks something like this:

template <class charT, class traits = char_traits<T> >
class basic_ios : public ios_base {
public:
    // ...

// Declaring the copy constructor and copy assignment operator private prohibits clients from calling them
private:
    basic_ios(const basic_ios&);  // not defined
    basic_ios& operator=(const basic_ios&);  // not defined
};

Note that:

  • basic_ios(const basic_ios&) is the copy constructor
  • basic_ios& operator=(const basic_ios&) is the copy assignment operator

(and both are private).

How does not defining these functions help?

  • Consider a case where a friend class or member functions try accessing these functions, then linking will fail because of missing function definitions.

In C++-11

In C++-11, the above can be done using = delete to mark the copy constructor and the copy assignment operator as deleted functions.

template <class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base {
public:
    // ...
    basic_ios(const basic_ios& ) = delete;  // deleted function
    basic_ios& operator=(const basic_ios&) = delete;  // deleted function
    // ...
};

It’s a convention to declare deleted functions public, but why? Better error messages.

In case you declare your deleted functions private, some compilers will probably complain about the function being private and can hide the error message of it not being usable (because it being deleted). Hence, it’s a good practice to make them public:

From my experience though, Apple’s clang compiler (v 12.0.5) doesn’t complain about it being private, but then - it can vary from compiler to compiler, so better to play safe. Here is an example of how the error message looks like:

#include <iostream>

class Sample {
public:
    Sample(int x) : x(x) { };
    // Copy constructor has been deleted, so should not be callable
    Sample(const Sample&) = delete;
    void trying_copy_construct(Sample s) {
        // This function tries to use a copy constructor
        // This should fail
        Sample new_object(s);
    }
private:
    int x;
};

Compiling the above code fails with the following error:

main.cpp:10:16: error: call to deleted constructor of 'Sample'
        Sample new_object(s);
               ^          ~
main.cpp:6:5: note: 'Sample' has been explicitly marked deleted here
    Sample(const Sample&) = delete;

Difference b/w using delete vs declaring private

Note: There is more to it except the good practice reasoning.

  • Using a deleted function in a member function or by a friend class won’t even compile the code if it tries to copy basic_ios objects while declaring private will compile successfully but fail during link-time.
  • Conventionally deleted functions are declared public, not private while the C++-98 way requires the functions to be declared private (see the section above for reasoning).
  • Only member functions can be private, while any function can be deleted (so you can delete some overloads for your function, in case you don’t want it to accept certain type inputs).

Let’s discuss the last point in detail:

Consider the case where you have:

template<typename T>
void processPointer(T* ptr);

And:

  • You need a template that works with built-in pointers. You want to reject calls with void* and char* pointers (more on this later, these deserve special handling at times).
  • Ideal way? Delete these instantiations (with void* or char* input pointers).
template<>
void processPointer<void>(void*) = delete;

template<>
void processPointer<char>(char*) = delete;

But, with C++-98 way, it’s not possible within the class scope. That is, you can not give template specialization to your member function within the class scope (it should be done in the namespace scope):

class Sample {
public:
    // ...
    template <typename T>
    void processPointer(T* ptr)
    { ... }

private:
    // ...
    // This is template specialization inside the scope of the class - not allowed
    template <>
    void processPointer<void>(void*);  // error
};

While with the C++-11 way, you can delete function outside the class scope:

class Sample {
public:
    // ...
    template <typename T>
    void processPointer(T* ptr)
    { ... }
    // ...
};

template <>
void Sample::processPointer<void>(void*) = delete;  // in public scope, and deleted!

I hope you liked this blog, thank you for reading! :)