With Explicitly Deleted Member Functions in C++11, Is It Still Worthwhile to Inherit from a Noncopyable Base Class

With explicitly deleted member functions in C++11, is it still worthwhile to inherit from a noncopyable base class?

Well, this:

private:
MyClass(const MyClass&) {}
MyClass& operator=(const MyClass&) {}

Still technically allows MyClass to be copied by members and friends. Sure, those types and functions are theoretically under your control, but the class is still copyable. At least with boost::noncopyable and = delete, nobody can copy the class.


I don't get why some people claim it's easier to make a class non-copyable in C++11.

It's not so much "easier" as "more easily digestible".

Consider this:

class MyClass
{
private:
MyClass(const MyClass&) {}
MyClass& operator=(const MyClass&) {}
};

If you are a C++ programmer who has read an introductory text on C++, but has little exposure to idiomatic C++ (ie: a lot of C++ programmers), this is... confusing. It declares copy constructors and copy assignment operators, but they're empty. So why declare them at all? Yes, they're private, but that only raises more questions: why make them private?

To understand why this prevents copying, you have to realize that by declaring them private, you make it so that non-members/friends cannot copy it. This is not immediately obvious to the novice. Nor is the error message that they will get when they try to copy it.

Now, compare it to the C++11 version:

class MyClass
{
public:
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
};

What does it take to understand that this class cannot be copied? Nothing more than understanding what the = delete syntax means. Any book explaining the syntax rules of C++11 will tell you exactly what that does. The effect of this code is obvious to the inexperienced C++ user.

What's great about this idiom is that it becomes an idiom because it is the clearest, most obvious way to say exactly what you mean.

Even boost::noncopyable requires a bit more thought. Yes, it's called "noncopyable", so it is self-documenting. But if you've never seen it before, it raises questions. Why are you deriving from something that can't be copied? Why do my error messages talk about boost::noncopyable's copy constructor? Etc. Again, understanding the idiom requires more mental effort.

Is deleting copy and move constructors/assignment operators in base class enough?

You cannot prevent a child class from defining its own copy/move constructor. That said, it will prevent it "out of the box", meaning if you do not provide one, or use a inline default constructor, it will also be marked as deleted. The reason you get a error here when you try to just define the constructor as default is because you are not allowed to do that in an out of line definition when a member or base has implicitly deleted it. Had you used

class DerivedClass : public BaseClass {
public:
DerivedClass(const DerivedClass &) = default;
bool doSomething() override;
};

then the code would compile, and you would only get an error if you actually try to call the copy constructor. This works because an inline implicit default is allowed even when a member or base implicitly deletes it and the end result is the constructor is implicitly deleted.

Can C++ classes with deleted methods be trivially copyable?

Yes, B is trivially copyable - regardless of what you do to non-special member functions.

N3337, §9/6:

A trivially copyable class is a class that:
— has no non-trivial
copy constructors (12.8),
— has no non-trivial move constructors
(12.8),
— has no non-trivial copy assignment operators (13.5.3, 12.8),

— has no non-trivial move assignment operators (13.5.3, 12.8), and

has a trivial destructor (12.4).


but the compound assignment is not a special method (right?)

No, it's not.

N3337, §12/1:

The default constructor (12.1), copy constructor and copy assignment
operator (12.8), move constructor and move assignment operator (12.8),
and destructor (12.4) are special member functions.

Why does overload resolution not fall back to constructor with reference to base class when copy-constructor is deleted?

Because = delete is a still a definition of the constructor (it is defined as deleted). Deleted functions are still possible candidates in overload resolution, and, in this case, the copy constructor is still the most viable candidate, and it is selected. Since a deleted function is referred to, the program is ill-formed.

From the standard, [dcl.fct.def.delete]:

  1. A deleted definition of a function is a function definition whose function-body is of the form = delete ; [...] A deleted function is a function with a deleted definition or a function that is implicitly defined as deleted.
  2. A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.

And it is pretty easy to see why a definition for B::B(B&) is more viable than B::B(Base&) for an lvalue of type B

Does access control matter for deleted constructors?

Since it's overload resolution that makes the program ill-formed in this case, and not access specifiers (which are checked later), there is no difference in outcome. The compiler will always complain that a deleted function was picked.

But since the idiom before C++11 was "declare but not define a private copy c'tor to disable copying", I would consider it going along with the same idiom, and therefore favorable. You are using the "old slang" with some new language to describe the same thing, except better.

How to handle classes that are not copyable?

Moveable types

You can make the class moveable by adding the move constructor and move assignment operator. (Some possibly useful overview of that.)

...
// rough sketch
// Having the path as `char*` seems
// bad (use std::string instead),
// but let's stick with the example as posted)
File(File&& other)
: path(nullptr)
, cfile(nullptr)
{
using std::swap;
// steal the guts of the moved-from object
swap(path, other.path);
swap(cfile, other.cfile);
}

The move assignment operator can be a little bit more tricky, what with self-assigment checks.

/or/

Smart pointers

You can wrap instances of these classes in a smart pointer and work with those:

  • std::shared_ptr, std::unique_ptr etc.
    std::unique_ptr<File> Search(const char* filepath) {
auto pfile = std::make_unique<File>(filepath);
...
return pfile;
}

This is especially helpful when you work with 3rd party classes (possibly legacy) that don't support move operations.

Yes, these have heap allocation overhead, but given the overhead of opening e.g. a file or acquiring another expensive resource that prevents copying, the heap allocation overhead might well be negligible.

Does a deleted constructor in base class influences on child class?

No, as the default copy constructor would call the parent's copy constructor (which is deleted), this won't work.

Why didn't you simply test it:

int main() {
auto x = fact_t<int>(5);
auto y = x;
}

Result:

copytest.cpp: In function 'int main()':
copytest.cpp:32:14: error: use of deleted function 'fact_t<int>::fact_t(const fact_t<int>&)'
auto y = x;
^
copytest.cpp:21:7: note: 'fact_t<int>::fact_t(const fact_t<int>&)' is implicitly declared as deleted because 'fact_t<int>' declares a move constructor or move assignment operator
class fact_t: public exp_t<val_t> {
^~~~~~

Force compiler to emit error on move constructor not defined with a base class deleted copy ctor

add

    NonCopyable(NonCopyable &&)                 = delete;
NonCopyable& operator=(NonCopyable &&) = delete;

that now complains about the base class move ctor being deleted.

C++ how noncopyable works?

Default copy constructor and assignment operator are generated by the compiler in the derived class and not added by the programmer

The implicit functions will try to call their counterparts in the base class. This won't be possible since those are private to the base class, so you'll get a compilation error. This is how the base class is intended to work.

Copy constructor and assignment operator are defined and declared public in the derived class by the programmer

Then you've defeated the purpose of inheriting from the base class; your derived class is now copyable via these functions.

Copy constructor and assignment operator are defined and declared private in the derived class by the programmer

Again, you've defeated the base class and made your class copyable; but only within its member and friend functions.



Related Topics



Leave a reply



Submit