Most concise way to disable copy and move semantics
Asked Answered
R

6

26

The following surely works but is very tedious:

T(const T&) = delete;
T(T&&) = delete;
T& operator=(const T&) = delete;
T& operator=(T&&) = delete;

I'm trying to discover the most concise way. Will the following work?

T& operator=(T) = delete;

Update

Note that I choose T& operator=(T) instead of T& operator=(const T&) or T& operator=(T&&), because it can serve both purposes.

Ruddy answered 19/2, 2018 at 11:15 Comment(5)
No. Use the 4-lines version to explicitly disable all 4.Buckskins
what about base class?Decarbonate
It seems in the standard preferably copy constructor and copy assignment operator are deleted. Compare this discussion.Irreverence
boost::noncopyable is a concise and expressive approach, if you don't mind paying for the dependency (or already have it)Bilberry
@Bilberry I have been somewhat sick of Boost since C++11, because its commitment of back compatibility with C++03.Ruddy
F
46

According to this chart (by Howard Hinnant):

meow

The most concise way is to =delete move assignment operator (or move constructor, but it can cause problems mentioned in comments).

Though, in my opinion the most readable way is to =delete both copy constructor and copy assignment operator.

Forensic answered 19/2, 2018 at 11:21 Comment(15)
It is a neat chart indeedAccentual
I'd also recommend only the assignment operator, and not the move c'tor. Explicitly deleting the c'tor can mess with RVOAccentual
@StoryTeller T& operator=(T); qualifies as a move assignment operator, right?Ruddy
@Ruddy - Not one that will affect the generation of the other special member functions. You'll still get a copy/move c'tor created for instance.Accentual
Note the red cells; those are defects in the standard design and a problem.Sulphurous
@Yakk they are half of problem, half of hassle. I actually not sure why declaring destructor must delete copy/move as some people suggestHangup
@swift uf tge destructor cleans up resources, copy duplicates them. What resources can be duplicated as bits but may be cleaned up twice harmlessly?Sulphurous
@Yakk those that can be shallow copied?Hangup
@Swift Wrong; not all resources that can be shallow copied can be cleaned up twice harmlessly. Remember, we have a non-trivial destructor. If you don't understand, feel free to press the [Ask Question] button to your top-right on this page; comments are not a Q&A zone.Sulphurous
My guideline is to never delete the move members.Ideography
Video description of Howard's chartIdeography
@Yakk not sure which nuance you refer but I can imagine (and had a lot of example in practice) with non-trivial destructor but default constructors (which were non-trivial due to class structure, and destructor's necessity was related to them). That was allowed by old compilers and if you cut off the top and right 2 rows and cols, you'd get a table for C++98. It might be backward compatibility thing. Constructor stops being trivial when virtual or static member exist. I think, same appyes for destructor, we can have default non-trivial both?Hangup
@Swift Comments are not a suitable place to ask and answer extended questions. If you want to learn about this further, press the [Ask Question] button to your top right. And ask the question there. I am not going to follow up here.Sulphurous
@HowardHinnant Learned much. Thanks :)Ruddy
@HowardHinnant: I think your suggestion fits well with my answer below.Agenda
A
5

Please don't try to find "the most concise way" to write a piece of code.

If there isn't an obvious form of expressing something which is also very concise - don't try to language-lawyer your way into writing a few characters less. Why? Think of people reading your code: If you need to consult the standard to realize your code does what you want it to do - then so will your code's readers. Except that they won't know what you're trying to achieve; so they won't consult the standard; so they'll just be confused about what your code does. Or - some will get it and some wont.*

In your case, if you make a subset of these deletions, or use some other "clever" trick - as a person reading your code, it's somewhat likely I will not catch on, not notice you're actually trying to get all copy and move semantics deleted. And I'll get confused, thinking you're trying to do something else. In fact, if I were you, I'd even consider adding a comment saying:

/* Disabling copy and move semantics because XYZ */
T(const T&) = delete;
T(T&&) = delete;
T& operator=(const T&) = delete;
T& operator=(T&&) = delete;

which is even more "tedious", but would make your intention/motivation absolutely clear to your future readers.

There's also the question of what exactly the "XYZ" reason is. Some would argue that there's no good reason to delete the move members, and it's generally a bad idea to do so. C++ luminary Howard Hinnant has this to say on the matter.

* - A variant on a principle I spelled out here.

Agenda answered 20/2, 2018 at 17:25 Comment(6)
You are assuming the most concise way is less expressive, which is not necessarily the case. The intention is clearer if it can be expressed concisely while maintaining precision.Bilberry
@ricab: See edit. The point is that "most concise" is not a virtue.Agenda
I maintain that it is, if it does not go against expressiveness. boost::noncopyable is a good example off achieving both IMOBilberry
@Bilberry but it isn't. I still had to doublecheck whether boost::noncopyable disables only copying or if it disables moving too. Moving a value is NOT copying.Haunted
@Kaihaku, you need to know what the building blocks used in the code do. That is always the case and better than rewrite the code each time. Going for noncopyable is essentially just a case of DRY. The name could probably be better, but you know, history...Bilberry
@ricab: Your exchange with Kaihaku just gave me another idea.Agenda
P
3

You can write a simple struct and inherit from it:

struct crippled
{
    crippled() = default;

    crippled(const crippled&) = delete;
    crippled(crippled&&) = delete;

    crippled& operator=(const crippled&) = delete;
    crippled& operator=(crippled&&) = delete;
};

Usage:

struct my_class : crippled
{

};

int main()
{
    my_class a;
    auto b = a; // fails to compile
}
Pi answered 19/2, 2018 at 11:25 Comment(2)
I believe Boost has a class like this. It would be more transparent to inherit from that instead of rolling your own (although not worth using Boost in your project if that's the only use).Tough
That would be boost::noncopyable (boost.org/doc/libs/1_65_0/libs/core/doc/html/core/…)Clotheshorse
B
3

I prefer to inherit from boost::noncopyable, thereby making the intention immediately clear and delegating the details to a trustworthy library.

#include <boost/core/noncopyable.hpp>

class X: private boost::noncopyable
{
};

It involves adding a dependency, but if you are ok with that, it is arguably a very concise and expressive way to accomplish it.

Bilberry answered 20/2, 2018 at 17:19 Comment(1)
See my variation on your answer.Agenda
L
3

I believe that in this case, macros are actually more readable:

#define NOT_COPYABLE( TypeName ) \
TypeName ( TypeName const& ) = delete; \
TypeName & operator = ( TypeName const& ) = delete;

#define NOT_MOVEABLE( TypeName ) \
TypeName ( TypeName && ) = delete; \
TypeName & operator = ( TypeName && ) = delete;
Labuan answered 25/2, 2018 at 3:52 Comment(0)
A
0

@ricab's answer suggested not inventing the wheel, and using boost::noncopyable as a mix-in base class. That does indeed work, but has the disadvantage of being confusing! ... as being non-copyable does not mean a class is non-movable. One would have to remember the historical circumstances for the naming of this boost class; most people sure don't, and neither do I.

Well, you could do the same, but renaming this class:

#include <boost/core/noncopyable.hpp>

namespace mixins {
using noncopyable_and_nonmovable = boost::noncopyable;
} // namespace mixins 

class X: private mixins::noncopyable_and_nonmovable {
  // etc. etc.
};

Now what you're doing is clear and obvious :-)

Also, you may want to consider creating a small namespace of mixins with this definition and possibly others (e.g. only non-copyable, only-non-movable etc.) into some utility header. That would make this solution even more concise to use.


I would still consider explaining the reason for the decision to disallow copying and moving somewhere; see my other answer.

Agenda answered 15/9, 2021 at 19:48 Comment(4)
OK yeah, that's probably a good idea. I don't know about "most people" but surely "some people", and that's enough if this helps them. Especially since docs for boost::noncopyable are silent with respect to move. It follows from language rules that they are deleted (see most voted answer), but may as well make it clear in the name.Bilberry
@ricab: It's a pretty safe assumption that most people using a library don't know the history of its development; and an ever safer assumption about a large library with a long history like Boost. I would absolutely not have guessed that boost::noncopyable is non-movable and I consider myself not completely clueless.Agenda
History may explain the name, but the docs for noncopyable say copy operations are deleted. It follows that no move operations are generated. I am all for names being the first source of documentation, but they are still not the only one. Anyway, you already got my upvote.Bilberry
@ricab: Well, unique pointers have copy ctors deleted, with move operators not deleted. So it's not an obvious assumption to make, that non-copyable class is non-movable. And we can't rely on people scrutinizing the mixin class' source.Agenda

© 2022 - 2024 — McMap. All rights reserved.