Related
There're plenty of articles discussing value semantics vs reference semantics, and maybe more trying to explain move semantics. However, No one has ever talked about the connection between value semantics and move semantics. Are they orthogonal concepts?
Note: This question is NOT about comparing value semantics vs move semantics, cause it is perfectly clear these two concepts are not "comparable". This question is about how they are connected, specifically (like #StoryTeller said), about discussing(how):
Move semantics help facilitate more use of value types.
From the original move proposal:
Copy vs Move
C and C++ are built on copy semantics. This is a Good Thing. Move
semantics is not an attempt to supplant copy semantics, nor undermine
it in any way. Rather this proposal seeks to augment copy semantics. A
general user defined class might be both copyable and movable, one or
the other, or neither.
The difference between a copy and a move is that a copy leaves the
source unchanged. A move on the other hand leaves the source in a
state defined differently for each type. The state of the source may
be unchanged, or it may be radically different. The only requirement
is that the object remain in a self consistent state (all internal
invariants are still intact). From a client code point of view,
choosing move instead of copy means that you don't care what happens
to the state of the source.
For PODs, move and copy are identical operations (right down to the
machine instruction level).
I guess one could add to this and say:
Move semantics allows us to keep value semantics, but at the same time gain the performance of reference semantics in those cases where the value of the original (copied-from) object is unimportant to program logic.
Inspired by Howard's answer, I wrote an article about this topic, hope it can help someone that's also wondering about it. I copy/paste the article here.
While I was learning move semantics, I always had a feeling, that even though I knew the concept quite well, I cannot fit it into the big picture of C++. Move semantics is not like some syntactic sugar that solely exists for convenience, it deeply affected the way people think and write C++ and has become one of the most important C++ idioms. But hey, the pond of C++ was already full of other idioms, when you throw move semantics in, mutual extrusion comes with it. Did move semantics break, enhance or replace other idioms? I don't know, but I want to find out.
Value Semantics
Value semantics is what makes me start to think about this problem. Since there aren't many things in C++ with the name "semantics", I naturally thought, "maybe value and move semantics have some connections?". Turns out, it's not just connections, it's the origin:
Move semantics is not an attempt to supplant copy semantics, nor undermine it in any way. Rather this proposal seeks to augment copy semantics.
- Move Semantics Proposal, September 10, 2002
Perhaps you've noticed it uses the wording "copy semantics", in fact, "value semantics" and "copy semantics" are the same thing, and I'll use them interchangeably.
OK, so what is value semantics? isocpp has a whole page talking about it, but basically, value semantics means assignment copies the value, like T b = a;. That's the definition, but often times value semantics just means to create, use, store the object itself, pass, return by value, rather than pointers or references.
The opposite concept is reference semantics, where assignment copies the pointer. In reference semantics, what's important is identity, for example T& b = a; , we have to remember that b is an alias of a, not anything else. But in value semantics, we don't care about identity at all, we only care about the value an object1 holds. This is brought by the nature of copy, because a copy is ensured to give us two independent objects that hold the same value, you can't tell which one is the source, nor does it affect usage.
Unlike other languages(Java, C#, JavaScript), C++ is built on value semantics. By default, assignment does bit-wise-copy(if no user-defined copy ctor is involved), arguments and return values are copy-constructed(yes I know there's RVO). Keeping value semantics is considered a good thing in C++. On the one hand, it's safer, because you don't need to worry about dangling pointers and all the creepy stuff; on the other hand, it's faster, because you have less indirection, see here for the official explanation.
Move Semantics: V8 Engine on the Value Semantics Car
Move semantics is not an attempt to supplant copy semantics. They are totally compatible with each other. I came up with this metaphor which I feel describes their relation really well.
Imagine you have a car, it ran smoothly with the built-in engine. One day, you installed an extra V8 engine onto this car. Whenever you have enough fuel, the V8 engine is able to speed up your car, and that makes you happy.
So, the car is value semantics, and the V8 engine is move semantics. Installing an engine on your car does not require a new car, it's still the same car, just like using move semantics won't make you drop value semantics, because you're still operating on the object itself not its references or pointers. Further more, the move if you can, else copy strategy, implemented by the binding preferences, is exactly like way engine is chosen, that is to use V8 if you can(enough fuel), otherwise fall back to the original engine.
Now we have a pretty good understanding of Howard Hinnant(main author of the move proposal)'s answer on SO:
Move semantics allows us to keep value semantics, but at the same time gain the performance of reference semantics in those cases where the value of the original (copied-from) object is unimportant to program logic.
EDIT: Howard added some comment that really worth mentioning. By definition, move semantics acts more like reference semantics, because the moved-to and moved-from objects are not independent, when modifying(either by move-construction or move-assignment) the moved-to object, the moved-from object is also modified. However, it doesn't really matter——when move semantics takes place, you don't care about the moved-from object, it's either a pure rvalue (so nobody else has a reference to the original), or when the programmer specifically says "I don't care about the value of the original after the copy" (by using std::move instead of copy). Since modification to the original object has no impact on the program, you can use the moved-to object as if it's an independent copy, retaining the appearance of value semantics.
Move Semantics and Performance Optimization
Move semantics is mostly about performance optimization: the ability to move an expensive object from one address in memory to another, while pilfering resources of the source in order to construct the target with minimum expense.
- Move Semantics Proposal
As stated in the proposal, the main benefit people get from move semantics are performance boost. I'll give two examples here.
The optimization you can see
Suppose we have a handler(whatever that is) which is expensive to construct, and we want to store it into a map for future use.
std::unordered_map<string, Handler> handlers;
void RegisterHandler(const string& name, Handler handler) {
handlers[name] = std::move(handler);
}
RegisterHandler("handler-A", build_handler());
This is a typical use of move, and of course it assumes Handler has a move ctor. By moving(not copying)-constructing a map value, a lot of time may be saved.
The optimization you can't see
Howard Hinnant once mentioned in his talk that, the idea of move semantics came from optimizing std::vector. How?
A std::vector<T> object is basically a set of pointers to an internal data buffer on heap, like begin() and end(). Copying a vector is expensive due to allocating new memory for the data buffer. When move is used instead of copy, only the pointers get copied and point to the old buffer.
What's more, move also boosts vector insert operation. This is explained in the vector Example section in the proposal. Say we have a std::vector<string> with two elements "AAAAA" and "BBBBB", now we want to insert "CCCCC" at index 1. Assuming the vector has enough capacity, the following graph demonstrates the process of inserting with copy vs move.
(source: qnssl.com)
Everything shown on the graph is on heap, including the vector's data buffer and each element string's data buffer. With copy, str_b's data buffer has to be copied, which involves a buffer allocation then deallocation. With move, old str_b's data buffer is reused by the new str_b in the new address, no buffer allocation or deallocation is needed(As Howard pointed out, the "data" that old str_b now points to is unspecified). This brings a huge performance boost, yet it means more than that, because now you can store expensive objects into a vector without sacrificing performance, while previously having to store pointers. This also helps extend usage of value semantics.
Move Semantics and Resource Management
In the famous article Rule of Zero, the author wrote:
Using value semantics is essential for RAII, because references don’t affect the lifetime of their referrents.
I found it to be a good starting point to discuss the correlation between move semantics and resource management.
As you may or may not know, RAII has another name called Scope-Bound Resource Management (SBRM), after the basic use case where the lifetime of an RAII object ends due to scope exit. Remember one advantage of using value semantics? Safety. We know exactly when an object's lifetime starts and ends, just by looking at its storage duration, and 99% of the time we'll find it at block scope, which makes it very simple. Things get a lot more complicated for pointers and references, now we have to worry about whether the object that is referenced or pointed to has been released. This is hard, what makes it worse is that these objects usually exist in different scope from its pointers and references.
It's obvious why value semantics gets along well with RAII —— RAII binds the life cycle of a resource to the lifetime of an object, and with value semantics, you have a clear idea of an object's lifetime.
But, resource is about identity…
Though value semantics and RAII seems to be a perfect match, in reality it was not. Why? Fundamentally speaking, because resource is about identity, while value semantics only cares about value. You have an open socket, you use the very socket; you have an open file, you use the very file. In the context of resource management, there aren't things with the same value. A resource represents itself, with unique identity.
See the contradiction here? Prior to C++11, if we stick with value semantics, it was hard to work with resources cause they cannot be copied, therefore programmers came up with some workarounds:
Use raw pointers;
Write their own movable-but-not-copyable class(often Involves private copy ctor and operations like swap and splice);
Use auto_ptr.
These solutions intended to solve the problem of unique ownership and ownership transferring, but they all have some drawbacks. I won't talk about it here cause it's everywhere on the Internet. What I would like to address is that, even without move semantics, resource ownership management can be done, it's just that it takes more code and is often error-prone.
What is lacking is uniform syntax and semantics to enable generic code to move arbitrary objects (just as generic code today can copy arbitrary objects).
- Move Semantics Proposal
Compared to the above statement from proposal, I like this answer more:
In addition to the obvious efficiency benefit, this also affords a programmer a standards-compliant way to have objects that are movable but not copyable. Objects that are movable and not copyable convey a very clear boundary of resource ownership via standard language semantics …my point is that move semantics is now a standard way to concisely express (among other things) movable-but-not-copyable objects.
The above quote has done a pretty good job explaining what move semantics means to resource ownership management in C++. Resource should naturally be movable(by "movable" I mean transferrable) but not copyable, now with the help of move semantics(well actually a whole lot of change at language level to support it), there's a standard way to do this right and efficiently.
The Rebirth of Value Semantics
Finally, we are able to talk about the other aspect(besides performance) of augmentation that move semantics brought to value semantics.
Stepping through the above discussion, we've seen why value semantics fits the RAII model, but at the same time not compatible with resource management. With the arise of move semantics, the necessary materials to fill this gap is finally prepared. So here we have, smart pointers!
Needless to say the importance of std::unique_ptr and std::shared_ptr, here I'd like to emphasize three things:
They follow RAII;
They take huge advantage of move semantics(especially for unique_ptr);
They help keep value semantics.
For the third point, if you've read Rule of Zero, you know what I'm talking about. No need to use raw pointers to manage resources, EVER, just use unique_ptr directly or store as member variable, and you're done. When transferring resource ownership, the implicitly constructed move ctor is able to do the job well. Better yet, the current specification ensures that, a named value in the return statement in the worst case(i.e. without elisions) is treated as an rvalue. It means, returning by value should be the default choice for unique_ptr.
std::unique_ptr<ExpensiveResource> foo() {
auto data = std::make_unique<ExpensiveResource>();
return data;
}
std::unique_ptr<ExpensiveResource> p = foo(); // a move at worst
See here for a more detailed explanation. In fact, when using unique_ptr as function parameters, passing by value is still the best choice. I'll probably write an article about it, if time is available.
Besides smart pointers, std::string and std::vector are also RAII wrappers, and the resource they manage is heap memory. For these classes, return by value is still preferred. I'm not too sure about other things like std::thread or std::lock_guard cause I haven't got chance to use them.
To summarize, by utilizing smart pointers, value semantics now truly gains compatibility with RAII. At its core, this is powered by move semantics.
Summary
So far we've gone through a lot of concepts and you probably feel overwhelmed, but the points I want to convey are simple:
Move semantics boosts performance while keeping value semantics;
Move semantics helps bring every piece of resource management together to become what it is today. In particular, it is the key that makes value semantics and RAII truly work together, as it should have been long ago.
I'm a learner on this topic myself, so feel free to point out anything that you feel is wrong, I really appreciate it.
[1]: Here object means "a piece of memory that has an address, a type, and is capable of storing values", from Andrzej's C++ blog.
I am basically trying to figure out, is the whole "move semantics" concept something brand new, or it is just making existing code simpler to implement? I am always interested in reducing the number of times I call copy/constructors but I usually pass objects through using reference (and possibly const) and ensure I always use initialiser lists. With this in mind (and having looked at the whole ugly && syntax) I wonder if it is worth adopting these principles or simply coding as I already do? Is anything new being done here, or is it just "easier" syntactic sugar for what I already do?
TL;DR
This is definitely something new and it goes well beyond just being a way to avoid copying memory.
Long Answer: Why it's new and some perhaps non-obvious implications
Move semantics are just what the name implies--that is, a way to explicitly declare instructions for moving objects rather than copying. In addition to the obvious efficiency benefit, this also affords a programmer a standards-compliant way to have objects that are movable but not copyable. Objects that are movable and not copyable convey a very clear boundary of resource ownership via standard language semantics. This was possible in the past, but there was no standard/unified (or STL-compatible) way to do this.
This is a big deal because having a standard and unified semantic benefits both programmers and compilers. Programmers don't have to spend time potentially introducing bugs into a move routine that can reliably be generated by compilers (most cases); compilers can now make appropriate optimizations because the standard provides a way to inform the compiler when and where you're doing standard moves.
Move semantics is particularly interesting because it very well suits the RAII idiom, which is a long-standing a cornerstone of C++ best practice. RAII encompasses much more than just this example, but my point is that move semantics is now a standard way to concisely express (among other things) movable-but-not-copyable objects.
You don't always have to explicitly define this functionality in order to prevent copying. A compiler feature known as "copy elision" will eliminate quite a lot of unnecessary copies from functions that pass by value.
Criminally-Incomplete Crash Course on RAII (for the uninitiated)
I realize you didn't ask for a code example, but here's a really simple one that might benefit a future reader who might be less familiar with the topic or the relevance of Move Semantics to RAII practices. (If you already understand this, then skip the rest of this answer)
// non-copyable class that manages lifecycle of a resource
// note: non-virtual destructor--probably not an appropriate candidate
// for serving as a base class for objects handled polymorphically.
class res_t {
using handle_t = /* whatever */;
handle_t* handle; // Pointer to owned resource
public:
res_t( const res_t& src ) = delete; // no copy constructor
res_t& operator=( const res_t& src ) = delete; // no copy-assignment
res_t( res_t&& src ) = default; // Move constructor
res_t& operator=( res_t&& src ) = default; // Move-assignment
res_t(); // Default constructor
~res_t(); // Destructor
};
Objects of this class will allocate/provision whatever resource is needed upon construction and then free/release it upon destruction. Since the resource pointed to by the data member can never accidentally be transferred to another object, the rightful owner of a resource is never in doubt. In addition to making your code less prone to abuse or errors (and easily compatible with STL containers), your intentions will be immediately recognized by any programmer familiar with this standard practice.
In the Turing Tar Pit, there is nothing new under the sun. Everything that move semantics does, can be done without move semantics -- it just takes a lot more code, and is a lot more fragile.
What move semantics does is takes a particular common pattern that massively increases efficiency and safety in a number of situations, and embeds it in the language.
It increases efficiency in obvious ways. Moving, be it via swap or move construction, is much faster for many data types than copying. You can create special interfaces to indicate when things can be moved from: but honestly people didn't do that. With move semantics, it becomes relatively easy to do. Compare the cost of moving a std::vector to copying it -- move takes roughly copying 3 pointers, while copying requires a heap allocation, copying every element in the container, and creating 3 pointers.
Even more so, compare reserve on a move-aware std::vector to a copy-only aware one: suppose you have a std::vector of std::vector. In C++03, that was performance suicide if you didn't know the dimensions of every component ahead of time -- in C++11, move semantics makes it as smooth as silk, because it is no longer repeatedly copying the sub-vectors whenever the outer vector resizes.
Move semantics makes every "pImpl pattern" type to have blazing fast performance, while means you can start having complex objects that behave like values instead of having to deal with and manage pointers to them.
On top of these performance gains, and opening up complex-class-as-value, move semantics also open up a whole host of safety measures, and allow doing some things that where not very practical before.
std::unique_ptr is a replacement for std::auto_ptr. They both do roughly the same thing, but std::auto_ptr treated copies as moves. This made std::auto_ptr ridiculously dangerous to use in practice. Meanwhile, std::unique_ptr just works. It represents unique ownership of some resource extremely well, and transfer of ownership can happen easily and smoothly.
You know the problem whereby you take a foo* in an interface, and sometimes it means "this interface is taking ownership of the object" and sometimes it means "this interface just wants to be able to modify this object remotely", and you have to delve into API documentation and sometimes source code to figure out which?
std::unique_ptr actually solves this problem -- interfaces that want to take onwership can now take a std::unique_ptr<foo>, and the transfer of ownership is obvious at both the API level and in the code that calls the interface. std::unique_ptr is an auto_ptr that just works, and has the unsafe portions removed, and replaced with move semantics. And it does all of this with nearly perfect efficiency.
std::unique_ptr is a transferable RAII representation of resource whose value is represented by a pointer.
After you write make_unique<T>(Args&&...), unless you are writing really low level code, it is probably a good idea to never call new directly again. Move semantics basically have made new obsolete.
Other RAII representations are often non-copyable. A port, a print session, an interaction with a physical device -- all of these are resources for whom "copy" doesn't make much sense. Most every one of them can be easily modified to support move semantics, which opens up a whole host of freedom in dealing with these variables.
Move semantics also allows you to put your return values in the return part of a function. The pattern of taking return values by reference (and documenting "this one is out-only, this one is in/out", or failing to do so) can be somewhat replaced by returning your data.
So instead of void fill_vec( std::vector<foo>& ), you have std::vector<foo> get_vec(). This even works with multiple return values -- std::tuple< std::vector<A>, std::set<B>, bool > get_stuff() can be called, and you can load your data into local variables efficiently via std::tie( my_vec, my_set, my_bool ) = get_stuff().
Output parameters can be semantically output-only, with very little overhead (the above, in a worst case, costs 8 pointer and 2 bool copies, regardless of how much data we have in those containers -- and that overhead can be as little as 0 pointer and 0 bool copies with a bit more work), because of move semantics.
There is absolutely something new going on here. Consider unique_ptr which can be moved, but not copied because it uniquely holds ownership of a resource. That ownership can then be transferred by moving it to a new unique_ptr if needed, but copying it would be impossible (as you would then have two references to the owned object).
While many uses of moving may have positive performance implications, the movable-but-not-copyable types are a much bigger functional improvement to the language.
In short, use the new techniques where it indicates the meaning of how your class should be used, or where (significant) performance concerns can be alleviated by movement rather than copy-and-destroy.
No answer is complete without a reference to Thomas Becker's painstakingly exhaustive write up on rvalue references, perfect forwarding, reference collapsing and everything related to that.
see here: http://thbecker.net/articles/rvalue_references/section_01.html
I would say yes because a Move Constructor and Move Assignment operator are now compiler defined for objects that do not define/protect a destructor, copy constructor, or copy assignment.
This means that if you have the following code...
struct intContainer
{
std::vector<int> v;
}
intContainer CreateContainer()
{
intContainer c;
c.v.push_back(3);
return c;
}
The code above would be optimized simply by recompiling with a compiler that supports move semantics. Your container c will have compiler defined move-semantics and thus will call the manually defined move operations for std::vector without any changes to your code.
Since move semantics only apply in the presence of rvalue
references, which are declared by a new token, &&, it seems
very clear that they are something new.
In principle, they are purely an optimizing techique, which
means that:
1. you don't use them until the profiler says it is necessary, and
2. in theory, optimizing is the compiler's job, and move
semantics aren't any more necessary than register.
Concerning 1, we may, in time, end up with an ubiquitous
heuristic as to how to use them: after all, passing an argument
by const reference, rather than by value, is also an
optimization, but the ubiquitous convention is to pass class
types by const reference, and all other types by value.
Concerning 2, compilers just aren't there yet. At least, the
usual ones. The basic principles which could be used to make
move semantics irrelevant are (well?) known, but to date, they
tend to result in unacceptable compile times for real programs.
As a result: if you're writing a low level library, you'll
probably want to consider move semantics from the start.
Otherwise, they're just extra complication, and should be
ignored, until the profiler says otherwise.
A problem of "value types" with external resources (like std::vector<T> or std::string) is that copying them tends to be quite expensive, and copies are created implicitly in various contexts, so this tends to be a performance concern. C++0x's answer to this problem is move semantics, which is conceptionally based on the idea of resource pilfering and technically powered by rvalue references.
Does D have anything similar to move semantics or rvalue references?
I believe that there are several places in D (such as returning structs) that D manages to make them moves whereas C++ would make them a copy. IIRC, the compiler will do a move rather than a copy in any case where it can determine that a copy isn't needed, so struct copying is going to happen less in D than in C++. And of course, since classes are references, they don't have the problem at all.
But regardless, copy construction already works differently in D than in C++. Generally, instead of declaring a copy constructor, you declare a postblit constructor: this(this). It does a full memcpy before this(this) is called, and you only make whatever changes are necessary to ensure that the new struct is separate from the original (such as doing a deep copy of member variables where needed), as opposed to creating an entirely new constructor that must copy everything. So, the general approach is already a bit different from C++. It's also generally agreed upon that structs should not have expensive postblit constructors - copying structs should be cheap - so it's less of an issue than it would be in C++. Objects which would be expensive to copy are generally either classes or structs with reference or COW semantics.
Containers are generally reference types (in Phobos, they're structs rather than classes, since they don't need polymorphism, but copying them does not copy their contents, so they're still reference types), so copying them around is not expensive like it would be in C++.
There may very well be cases in D where it could use something similar to a move constructor, but in general, D has been designed in such a way as to reduce the problems that C++ has with copying objects around, so it's nowhere near the problem that it is in C++.
I think all answers completely failed to answer the original question.
First, as stated above, the question is only relevant for structs. Classes have no meaningful move. Also stated above, for structs, a certain amount of move will happen automatically by the compiler under certain conditions.
If you wish to get control over the move operations, here's what you have to do. You can disable copying by annotating this(this) with #disable. Next, you can override C++'s constructor(constructor &&that) by defining this(Struct that). Likewise, you can override the assign with opAssign(Struct that). In both cases, you need to make sure that you destroy the values of that.
For assignment, since you also need to destroy the old value of this, the simplest way is to swap them. An implementation of C++'s unique_ptr would, therefore, look something like this:
struct UniquePtr(T) {
private T* ptr = null;
#disable this(this); // This disables both copy construction and opAssign
// The obvious constructor, destructor and accessor
this(T* ptr) {
if(ptr !is null)
this.ptr = ptr;
}
~this() {
freeMemory(ptr);
}
inout(T)* get() inout {
return ptr;
}
// Move operations
this(UniquePtr!T that) {
this.ptr = that.ptr;
that.ptr = null;
}
ref UniquePtr!T opAssign(UniquePtr!T that) { // Notice no "ref" on "that"
swap(this.ptr, that.ptr); // We change it anyways, because it's a temporary
return this;
}
}
Edit:
Notice I did not define opAssign(ref UniquePtr!T that). That is the copy assignment operator, and if you try to define it, the compiler will error out because you declared, in the #disable line, that you have no such thing.
D have separate value and object semantics :
if you declare your type as struct, it will have value semantic by default
if you declare your type as class, it will have object semantic.
Now, assuming you don't manage the memory yourself, as it's the default case in D - using a garbage collector - you have to understand that object of types declared as class are automatically pointers (or "reference" if you prefer) to the real object, not the real object itself.
So, when passing vectors around in D, what you pass is the reference/pointer. Automatically. No copy involved (other than the copy of the reference).
That's why D, C#, Java and other language don't "need" moving semantic (as most types are object semantic and are manipulated by reference, not by copy).
Maybe they could implement it, I'm not sure. But would they really get performance boost as in C++? By nature, it don't seem likely.
I somehow have the feeling that actually the rvalue references and the whole concept of "move semantics" is a consequence that it's normal in C++ to create local, "temporary" stack objects. In D and most GC languages, it's most common to have objects on the heap, and then there's no overhead with having a temporary object copied (or moved) several times when returning it through a call stack - so there's no need for a mechanism to avoid that overhead too.
In D (and most GC languages) a class object is never copied implicitly and you're only passing the reference around most of the time, so this may mean that you don't need any rvalue references for them.
OTOH, struct objects are NOT supposed to be "handles to resources", but simple value types behaving similar to builtin types - so again, no reason for any move semantics here, IMHO.
This would yield a conclusion - D doesn't have rvalue refs because it doesn't need them.
However, I haven't used rvalue references in practice, I've only had a read on them, so I might have skipped some actual use cases of this feature. Please treat this post as a bunch of thoughts on the matter which hopefully would be helpful for you, not as a reliable judgement.
I think if you need the source to loose the resource you might be in trouble. However being GC'ed you can often avoid needing to worry about multiple owners so it might not be an issue for most cases.
I have a medium complex C++ class which holds a set of data read from disc. It contains an eclectic mix of floats, ints and structures and is now in general use. During a major code review it was asked whether we have a custom assignment operator or we rely on the compiler generated version and if so, how do we know it works correctly? Well, we didn't write a custom assignment and so a unit test was added to check that if we do:
CalibDataSet datasetA = getDataSet();
CalibDataSet dataSetB = datasetA;
then datasetB is the same as datasetA. A couple of hundred lines or so. Now the customer inists that we cannot rely on the compiler (gcc) being correct for future releases and we should write our own. Are they right to insist on this?
Additional info:
I'm impressed by the answers/comments already posted and the response time.Another way of asking this question might be:
When does a POD structure/class become a 'not' POD structure/class?
It is well-known what the automatically-generated assignment operator will do - that's defined as part of the standard and a standards-compliant C++ compiler will always generate a correctly-behaving assignment operator (if it didn't, then it would not be a standards-compliant compiler).
You usually only need to write your own assignment operator if you've written your own destructor or copy constructor. If you don't need those, then you don't need an assignment operator, either.
The most common reason to explicitly define an assignment operator is to support "remote ownership" -- basically, a class that includes one or more pointers, and owns the resources to which those pointers refer. In such a case, you normally need to define assignment, copying (i.e., copy constructor) and destruction. There are three primary strategies for such cases (sorted by decreasing frequency of use):
Deep copy
reference counting
Transfer of ownership
Deep copy means allocating a new resource for the target of the assignment/copy. E.g., a string class has a pointer to the content of the string; when you assign it, the assignment allocates a new buffer to hold the new content in the destination, and copies the data from the source to the destination buffer. This is used in most current implementations of a number of standard classes such as std::string and std::vector.
Reference counting used to be quite common as well. Many (most?) older implementations of std::string used reference counting. In this case, instead of allocating and copying the data for the string, you simply incremented a reference count to indicate the number of string objects referring to a particular data buffer. You only allocated a new buffer when/if the content of a string was modified so it needed to differ from others (i.e., it used copy on write). With multithreading, however, you need to synchronize access to the reference count, which often has a serious impact on performance, so in newer code this is fairly unusual (mostly used when something stores so much data that it's worth potentially wasting a bit of CPU time to avoid such a copy).
Transfer of ownership is relatively unusual. It's what's done by std::auto_ptr. When you assign or copy something, the source of the assignment/copy is basically destroyed -- the data is transferred from one to the other. This is (or can be) useful, but the semantics are sufficiently different from normal assignment that it's often counterintuitive. At the same time, under the right circumstances, it can provide great efficiency and simplicity. C++0x will make transfer of ownership considerably more manageable by adding a unique_ptr type that makes it more explicit, and also adding rvalue references, which make it easy to implement transfer of ownership for one fairly large class of situations where it can improve performance without leading to semantics that are visibly counterintuitive.
Going back to the original question, however, if you don't have remote ownership to start with -- i.e., your class doesn't contain any pointers, chances are good that you shouldn't explicitly define an assignment operator (or dtor or copy ctor). A compiler bug that stopped implicitly defined assignment operators from working would prevent passing any of a huge number of regression tests.
Even if it did somehow get released, your defense against it would be to just not use it. There's no real room for question that such a release would be replaced within a matter of hours. With a medium to large existing project, you don't want to switch to a compiler until it's been in wide use for a while in any case.
If the compiler isn't generating the assignment properly, then you have bigger problems to worry about than implementing the assignment overload (like the fact that you have a broken compiler). Unless your class contains pointers, it is not necessary to provide your own overload; however, it is reasonable to request an explicit overload, not because the compiler might break (which is absurd), but rather to document your intention that assignment be permitted and behave in that manner. In C++0x, it will be possible to document intent and save time by using = default for the compiler-generated version.
If you have some resources in your class that you are supposed to manage then writing your own assignment operator makes sense else rely on compiler generated one.
I guess the problem of shallow copy deep copy may appear in case you are dealing with strings.
http://www.learncpp.com/cpp-tutorial/912-shallow-vs-deep-copying/
but i think it is always advisable to write assignment overloads for user defined classes.
I have a Shape class containing potentially many vertices, and I was contemplating making copy-constructor/copy-assignment private to prevent accidental needless copying of my heavyweight class (for example, passing by value instead of by reference).
To make a copy of Shape, one would have to deliberately call a "clone" or "duplicate" method.
Is this good practice? I wonder why STL containers don't use this approach, as I rarely want to pass them by value.
Restricting your users isn't always a good idea. Just documenting that copying may be expensive is enough. If a user really wants to copy, then using the native syntax of C++ by providing a copy constructor is a much cleaner approach.
Therefore, I think the real answer depends on the context. Perhaps the real class you're writing (not the imaginary Shape) shouldn't be copied, perhaps it should. But as a general approach, I certainly can't say that one should discourage users from copying large objects by forcing them to use explicit method calls.
IMHO, providing a copy constructor and assignment operator or not depend more of what your class modelizes than the cost of copying.
If your class represent values, that is if passing an object or a copy of the object doesn't make a difference, then provide them (and provide the equality operator also)
If your class isn't, that is if you think that object of the class have an identity and a state (one also speak of entities), don't. If a copy make sense, provide it with a clone or copy member.
There are sometimes classes you can't easily classify. Containers are in that position. It is meaninfull the consider them as entities and pass them only by reference and have special operations to make a copy when needed. You can also consider them simply as agregation of values and so copying makes sense. The STL was designed around value types. And as everything is a value, it makes sense for containers to be so. That allows things like map<int, list<> > which are usefull. (Remember, you can't put nocopyable classes in an STL container).
Generally, you do not make classes non-copyable just because they are heavy (you had shown a good example STL).
You make them non-copyable when they connected to some non-copyable resource like socket, file, lock or they are not designed to be copied at all (for example have some internal structures that can be hardly deep copied).
However, in your case your object is copyable so leave it as this.
Small note about clone() -- it is used as polymorphic copy constructor -- it has different
meaning and used differently.
Most programmers are already aware of the cost of copying various objects, and know how to avoid copies, using techniques such as pass by reference.
Note the STL's vector, string, map, list etc. could all be variously considered 'heavyweight' objects (especially something like a vector with 10,000 elements!). Those classes all still provide copy constructors and assignment operators, so if you know what you're doing (such as making a std::list of vectors), you can copy them when necessary.
So if it's useful, provide them anyway, but be sure to document they are expensive operations.
Depending on your needs...
If you want to ensure that a copy won't happen by mistake, and making a copy would cause a severe bottleneck or simply doesn't make sense, then this is good practice. Compiling errors are better than performance investigations.
If you are not sure how your class will be used, and are unsure if it's a good idea or not then it is not good practice. Most of the time you would not limit your class in this way.