Why don't C++ compilers define operator== and operator!=? - c++

I am a big fan of letting the compiler do as much work for you as possible. When writing a simple class the compiler can give you the following for 'free':
A default (empty) constructor
A copy constructor
A destructor
An assignment operator (operator=)
But it cannot seem to give you any comparison operators - such as operator== or operator!=. For example:
class foo
{
public:
std::string str_;
int n_;
};
foo f1; // Works
foo f2(f1); // Works
foo f3;
f3 = f2; // Works
if (f3 == f2) // Fails
{ }
if (f3 != f2) // Fails
{ }
Is there a good reason for this? Why would performing a member-by-member comparison be a problem? Obviously if the class allocates memory then you'd want to be careful, but for a simple class surely the compiler could do this for you?

The argument that if the compiler can provide a default copy constructor, it should be able to provide a similar default operator==() makes a certain amount of sense. I think that the reason for the decision not to provide a compiler-generated default for this operator can be guessed by what Stroustrup said about the default copy constructor in "The Design and Evolution of C++" (Section 11.4.1 - Control of Copying):
I personally consider it unfortunate
that copy operations are defined by
default and I prohibit copying of
objects of many of my classes.
However, C++ inherited its default
assignment and copy constructors from
C, and they are frequently used.
So instead of "why doesn't C++ have a default operator==()?", the question should have been "why does C++ have a default assignment and copy constructor?", with the answer being those items were included reluctantly by Stroustrup for backwards compatibility with C (probably the cause of most of C++'s warts, but also probably the primary reason for C++'s popularity).
For my own purposes, in my IDE the snippet I use for new classes contains declarations for a private assignment operator and copy constructor so that when I gen up a new class I get no default assignment and copy operations - I have to explicitly remove the declaration of those operations from the private: section if I want the compiler to be able to generate them for me.

Even in C++20, the compiler still won't implicitly generate operator== for you
struct foo
{
std::string str;
int n;
};
assert(foo{"Anton", 1} == foo{"Anton", 1}); // ill-formed
But you will gain the ability to explicitly default == since C++20:
struct foo
{
std::string str;
int n;
// either member form
bool operator==(foo const&) const = default;
// ... or friend form
friend bool operator==(foo const&, foo const&) = default;
};
Defaulting == does member-wise == (in the same way that the default copy constructor does member-wise copy construction). The new rules also provide the expected relationship between == and !=. For instance, with the declaration above, I can write both:
assert(foo{"Anton", 1} == foo{"Anton", 1}); // ok!
assert(foo{"Anton", 1} != foo{"Anton", 2}); // ok!
This specific feature (defaulting operator== and symmetry between == and !=) comes from one proposal that was part of the broader language feature that is operator<=>.

The compiler wouldn't know whether you wanted a pointer comparison or a deep (internal) comparison.
It's safer to just not implement it and let the programmer do that themselves. Then they can make all the assumptions they like.

IMHO, there is no "good" reason. The reason there are so many people that agree with this design decision is because they did not learn to master the power of value-based semantics. People need to write a lot of custom copy constructor, comparison operators and destructors because they use raw pointers in their implementation.
When using appropriate smart pointers (like std::shared_ptr), the default copy constructor is usually fine and the obvious implementation of the hypothetical default comparison operator would be as fine.

It's answered C++ didn't do == because C didn't, and here is why C provides only default = but no == at first place.
C wanted to keep it simple:
C implemented = by memcpy; however, == cannot be implemented by memcmp due to padding.
Because padding is not initialized, memcmp says they are different even though they are the same.
The same problem exists for empty class: memcmp says they are different because size of empty classes are not zero.
It can be seen from above that implementing == is more complicated than implementing = in C.
Some code example regarding this.
Your correction is appreciated if I'm wrong.

In this video Alex Stepanov, the creator of STL addresses this very question at about 13:00. To summarize, having watched the evolution of C++ he argues that:
It's unfortunate that == and != are not implicitly declared (and Bjarne agrees with him). A correct language should have those things ready for you (he goes further on to suggest you should not be able to define a != that breaks the semantics of ==)
The reason this is the case has its roots (as many of C++ problems) in C. There, the assignment operator is implicitly defined with bit by bit assignment but that wouldn't work for ==. A more detailed explanation can be found in this article from Bjarne Stroustrup.
In the follow up question Why then wasn't a member by member comparison used he says an amazing thing : C was kind of a homegrown language and the guy implementing these stuff for Ritchie told him he found this to be hard to implement!
He then says that in the (distant) future == and != will be implicitly generated.

C++20 provides a way to easily implement a default comparison operator.
Example from cppreference.com:
class Point {
int x;
int y;
public:
auto operator<=>(const Point&) const = default;
// ... non-comparison functions ...
};
// compiler implicitly declares operator== and all four relational operators work
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } // ok, calls implicit Point::operator==
std::set<Point> s; // ok
s.insert(pt1); // ok
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to Point::operator<=>

It is not possible to define default ==, but you can define default != via == which you usually should define yourselves.
For this you should do following things:
#include <utility>
using namespace std::rel_ops;
...
class FooClass
{
public:
bool operator== (const FooClass& other) const {
// ...
}
};
You can see http://www.cplusplus.com/reference/std/utility/rel_ops/ for details.
In addition if you define operator< , operators for <=, >, >= can be deduced from it when using std::rel_ops.
But you should be careful when you use std::rel_ops because comparison operators can be deduced for the types you are not expected for.
More preferred way to deduce related operator from basic one is to use boost::operators.
The approach used in boost is better because it define the usage of operator for the class you only want, not for all classes in scope.
You can also generate "+" from "+=", - from "-=", etc... (see full list here)

C++0x has had a proposal for default functions, so you could say default operator==;
We've learnt that it helps to make these things explicit.

Conceptually it is not easy to define equality. Even for POD data, one could argue that even if the fields are the same, but it is a different object (at a different address) it is not necessarily equal. This actually depends on the usage of the operator. Unfortunately your compiler is not psychic and cannot infer that.
Besides this, default functions are excellent ways to shoot oneself in the foot. The defaults you describe are basically there to keep compatibility with POD structs. They do however cause more than enough havoc with developers forgetting about them, or the semantics of the default implementations.

Just so that the answers to this question remains complete as the time passes by: since C++20 it can be automatically generated with command auto operator<=>(const foo&) const = default;
It will generate all the operators: ==, !=, <, <=, >, and >=, see https://en.cppreference.com/w/cpp/language/default_comparisons for details.
Due to operator's look <=>, it is called a spaceship operator. Also see Why do we need the spaceship <=> operator in C++?.
EDIT: also in C++11 a pretty neat substitute for that is available with std::tie see https://en.cppreference.com/w/cpp/utility/tuple/tie for a complete code example with bool operator<(…). The interesting part, changed to work with == is:
#include <tuple>
struct S {
………
bool operator==(const S& rhs) const
{
// compares n to rhs.n,
// then s to rhs.s,
// then d to rhs.d
return std::tie(n, s, d) == std::tie(rhs.n, rhs.s, rhs.d);
}
};
std::tie works with all comparison operators, and is completely optimized away by the compiler.

Is there a good reason for this? Why would performing a member-by-member comparison be a problem?
It may not be a problem functionally, but in terms of performance, default member-by-member comparison is liable to be more sub-optimal than default member-by-member assignment/copying. Unlike order of assignment, order of comparison impacts performance because the first unequal member implies the rest can be skipped. So if there are some members that are usually equal you want to compare them last, and the compiler doesn't know which members are more likely to be equal.
Consider this example, where verboseDescription is a long string selected from a relatively small set of possible weather descriptions.
class LocalWeatherRecord {
std::string verboseDescription;
std::tm date;
bool operator==(const LocalWeatherRecord& other){
return date==other.date
&& verboseDescription==other.verboseDescription;
// The above makes a lot more sense than
// return verboseDescription==other.verboseDescription
// && date==other.date;
// because some verboseDescriptions are liable to be same/similar
}
}
(Of course the compiler would be entitled to disregard the order of comparisons if it recognizes that they have no side-effects, but presumably it would still take its que from the source code where it doesn't have better information of its own.)

I agree, for POD type classes then the compiler could do it for you. However what you might consider simple the compiler might get wrong. So it is better to let the programmer do it.
I did have a POD case once where two of the fields were unique - so a comparison would never be considered true. However the comparison I needed only ever compared on the payload - something the compiler would never understand or could ever figure out on it's own.
Besides - they don't take long to write do they?!

Related

Are there situations when self-assignment is useful?

It is commonly known that when implementing an assignment operator one has to protect against self-assignment, at least when the class has non-POD members. Usually it is (or is equivalent to):
Foo& operator=(const Foo& other)
{
if (&other == this)
return *this;
... // Do copy
}
What were the reasons for not inserting the self-assignment protection automatically? Are there use cases when self-assignment does something non-trivial and practically useful?
Foo& operator=(const Foo& other)
{
if (&other == this)
{
// Do something non-trivial
}
else
{
// Do copy
}
return *this;
}
To summarize the answers and discussion by now
Looks like non-trivial self-assignment can never be really useful. The only option proposed was to put an assert there in order to detect some logical errors. But there are quite legitimate self-assignment cases like a = std::min(a, b), so even this option is highly dubious.
But there are two possible implementations of a trivial self-assignment:
Do nothing if &other == this. Always work, though may have negative performance impact due to an extra branching. But in a user-defined assignment operator the test must be almost always explicitly made.
Copy each member to itself. This is what is done by default. If the members use default assignment operators as well, it may be faster, because doesn't requre an extra branching.
I still don't see why the C++ standard could not guarantee that in a user-defined assignment operator &other != this. If you want no branching, use the default operator. If you are redefining the operator, some test is needed anyway...
Self-assignment protection is only necessary for types where the code being skipped is dangerous when applied to itself. Consider the case where you have a user-provided assignment operator because each individual object has some kind of identifier, which you don't want to copy. Well, you can "copy" the other values just fine in self-assignment cases. So inserting an invisible self-assignment test is just adding a pointless and potentially costly conditional branch.
So it's not about self-assignment being useful; it's about self-assignment not always needing protection.
Furthermore, C++ generally doesn't like adding code like that to your code without you explicitly asking for it. It's typically done in terms of whole functions, not part of functions. Even destructor calls at the end of blocks are something you asked for when you put the object to be destroyed on the stack.
There are algorithms where it can happen.
You know the lhs and rhs might be the same but it is just simpler to do the assignment than check. E.g., consider a = std::min(a,b); - simpler and perhaps easier to understand than if (a > b) a = b; - now consider more complicated examples of similar things.
You don't know whether lhs and rhs might be the same, because they may have been passed in from somewhere else.
These algorithms where it can happen are not uncommon.
I should admit I have never heard about the common knowledge like this. For a non-POD objects, a more strict approach is to forbid copying them with disabling copy constructor and the assignment operator. So that you don't have the problem at all.
If you still need to copy the class, but there is some data which is unsafe to copy to itself, you could only override assignment for that data, and when it is used as a field, it would be used by an automatic assignment implementation of the upper level class.
As for your question, if you only need to skip doing anything the automatic "self-assignment protection" is there already, in a way. If you don't define assignment operation explicitely and let compiler use the automatic one, after inlining self-assignment may become no-op.
For example, the following code:
class A {
int a;
double b;
};
A& foo(A& input)
{
return (input = input);
}
is compiled to (gcc 4.9, -O2):
_Z3fooR1A:
.cfi_startproc
movq %rdi, %rax
ret
.cfi_endproc
Which does not copy anything.

Copy constructors with move-only, but cloneable types in member containers

Assume that we have two types, T1 and T2.
T1 isn't important except the following facts:
it isn't copy constructible
it has a move constructor
we have an excellent function with the signature T1 copy(T1 const& orig), which creates a copy.
T2 can be simplified to the following class:
// T2.h
class T2 {
public:
T2() { /* initializes the vector with something */ }
T2(T2 const& other);
private:
std::vector<T1> v;
}
// T2.cpp
T2::T2(T2 const& other) : ... {}
How would you implement this method, if you could only write to the ellipsis part, or to the global scope?
A simple real world use case - assuming the "you can't write anything between the curly braces" part is a real world restriction:
T1 is std::unique_ptr<anything>
copy is std::make_unique
anything has a copy constructor
I also have two additional requirements for the implementation:
performance. It shouldn't be (considerably) slower than the naive implementation with a for loop in the copy constructor's body.
readability. The entire point behind the question is to do something which is more clear/clean than the trivial for loop (e.g. imagine T2 with two or more member vectors).
And optional, but nice to have features:
something that's easily generalized to other containers
something that works with just iterators
something that's generic
A clarification: I know the question is trivially solvable with a std::vector<T1> copy_vec(std::vector<T1> const& orig) global function. Placing that function into an anonymous namespace within T2.cpp would also make it local, but I would argue against its readability, I think it wouldn't be better than the for loop at all. And it's clearly a bad solution if the copy constructor isn't in an implementation file but inlined in the header.
So a rephrasing of my question is:
Is there already something similar implemented, which I can just include?
If there is none, then why? I'm not saying I thought about every corner case, but I think this is something that possibly can be implemented in a nice generic way, and thanks to unique_ptr, it's a common enough case.
Nothing wrong with a naive loop:
v.reserve(other.v.size());
for (auto& elem : other.v) {
v.push_back(copy(elem));
}
That's plenty readable and optimal.
Though I guess the modern, clever solution with be to use range-v3:
T2(T2 const& other)
: v(other.v | view::transform(copy))
{ }
I'm not sure that's enough better than the loop to justify the additional complexity but YMMV.

Is std::swap() safe to use for objects?

In this question about copy-and-swap, there is a swap() function in the accepted answer.
friend void swap(dumb_array& first, dumb_array& second) // nothrow
{
// enable ADL (not necessary in our case, but good practice)
using std::swap;
// by swapping the members of two classes,
// the two classes are effectively swapped
swap(first.mSize, second.mSize);
swap(first.mArray, second.mArray);
}
Why not just std::swap(first, second)?
std::swap relies on the assignment operator. So if the assignment operator were to call std::swap, this would lead to an infinite chain of calls back and forth between the assignment operator and std::swap.
Regarding the question in your title. Yes, for a well-behaved class with value semantics, it is safe to call std::swap on objects of that class. Only the implementer of the class cannot use it for the copy-and-swap idiom for the reason stated above.
The code snippet is suggested for C++98, there is another suggestion for C++11 (move semantics) in the same answer.
The reason why there is swap() function for C++98 solution is very well explained in Scott Meyers' Effective C++ book.
Briefly;
template<typename T> // typical implementation of std::swap;
void swap(T& a, T& b) // swaps a's and b's values
{
T temp(a);
a = b;
b = temp;
}
As long as your types support copying (via copy constructor and copy
assignment operator), the default swap implementation will let objects
of your types be swapped without your having to do any special work to
support it.
However, the default swap implementation may not thrill you. It
involves copying three objects: a to temp, b to a, and temp to b. For
some types, none of these copies are really necessary. For such types,
the default swap puts you on the fast track to the slow lane.
Foremost among such types are those consisting primarily of a pointer
to another type that contains the real data. A common manifestation of
this design approach is the "pimpl idiom"...

Move semantics - what it's all about? [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Can someone please explain move semantics to me?
Could someone point me to a good source or explain it here what are the move semantics?
Forget about C++0x for the moment. Move semantics are something that is language independent -- C++0x merely provides a standard way to perform operations with move semantics.
Definition
Move semantics define the behaviour of certain operations. Most of the time they are contrasted with copy semantics, so it would be useful to define them first.
Assignment with copy semantics has the following behaviour:
// Copy semantics
assert(b == c);
a = b;
assert(a == b && b == c);
i.e. a ends up equal to b, and we leave b unchanged.
Assignment with move semantics has weaker post conditions:
// Move semantics
assert(b == c);
move(a, b); // not C++0x
assert(a == c);
Note that there is no longer any guarantee that b remains unchanged after the assignment with move semantics. This is the crucial difference.
Uses
One benefit of move semantics is that it allows optimisations in certain situations. Consider the following regular value type:
struct A { T* x; };
Assume also that we define two objects of type A to be equal iff their x member point to equal values.
bool operator==(const A& lhs, const A& rhs) { return *lhs.x == *rhs.x; }
Finally assume that we define an object A to have sole ownership over the pointee of their x member.
A::~A() { delete x; }
A::A(const A& rhs) : x(new T(rhs.x)) {}
A& A::operator=(const A& rhs) { if (this != &rhs) *x = *rhs.x; }
Now suppose we want to define a function to swap two A objects.
We could do it the normal way with copy semantics.
void swap(A& a, A& b)
{
A t = a;
a = b;
b = t;
}
However, this is unnecessarily inefficient. What are we doing?
We create a copy of a into t.
We then copy b into a.
Then copy t into b.
Finally, destroy t.
If T objects are expensive to copy then this is wasteful. If I asked you to swap two files on your computer, you wouldn't create a third file then copy and paste the file contents around before destroying your temporary file, would you? No, you'd move one file away, move the second into the first position, then finally move the first file back into the second. No need to copy data.
In our case, it's easy to move around objects of type A:
// Not C++0x
void move(A& lhs, A& rhs)
{
lhs.x = rhs.x;
rhs.x = nullptr;
}
We simply move rhs's pointer into lhs and then relinquish rhs ownership of that pointer (by setting it to null). This should illuminate why the weaker post condition of move semantics allows optimisations.
With this new move operation defined, we can define an optimised swap:
void swap(A& a, A& b)
{
A t;
move(t, a);
move(a, b);
move(b, t);
}
Another advantage of move semantics is that it allows you to move around objects that are unable to be copied. A prime example of this is std::auto_ptr.
C++0x
C++0x allows move semantics through its rvalue reference feature. Specifically, operations of the kind:
a = b;
Have move semantics when b is an rvalue reference (spelt T&&), otherwise they have copy semantics. You can force move semantics by using the std::move function (different from the move I defined earlier) when b is not an rvalue reference:
a = std::move(b);
std::move is a simple function that essentially casts its argument to an rvalue reference. Note that the results of expressions (such as a function call) are automatically rvalue references, so you can exploit move semantics in those cases without changing your code.
To define move optimisations, you need to define a move constructor and move assignment operator:
T::T(T&&);
T& operator=(T&&);
As these operations have move semantics, you are free to modify the arguments passed in (provided you leave the object in a destructible state).
Conclusion
That's essentially all there is to it. Note that rvalue references are also used to allow perfect forwarding in C++0x (due to the specifically crafted type system interactions between rvalue references and other types), but this isn't really related to move semantics, so I haven't discussed it here.
Basically, rvalue references allow you to detect when objects are temporaries and you don't have to preserve their internal state. This allows for much more efficient code where C++03 used to have to copy all the time, in C++0x you can keep re-using the same resources. In addition, rvalue references enable perfect forwarding.
Have a look at this answer.
I read a ton of text explanations for about a year and didn't grasp everything about r-value references until I watch this excellent presentation by Scott Meyer : http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvalue-references
He explain in a way that is funny and slow enough to understand each thing that happens in the processes.
I know, it 1h30 but really, it's the best explanation I've had in the last year.
After having read the articles (like the other answers), watching this video did melt it together in my mind in a consistent way and few days after I was able to explain it to some colleagues and explain how to use std::unique_ptr (as it is related - it only allow move semantics, not copy) because it requires understanding of std::move(), that requires understanding move semantics.
glad to see such a question and I'm happy to share my point. I think you are asking about a bug-fix on the designation of the C++ language itself, not just another C++ language feature. The "bug" has been there for tens of year. That is, the copy constructor.
Copy constructors seems very strange if you know in physics there are lots of things that can not be copied like energy and mass. That's just a joke, but in fact in the world of programming too, objects like exclusive file descriptors are not copyable. So C++ programmers and designers invented some tricks to deal with that. There are 3 famous: NRVO, boost::noncopyable and std::auto_ptr.
NRVO (Named Return Value Optimization) is a technic that lets a function returns an object by value without calling the copy constructor. But the problem with NRVO is that though the copy constructor is not actually called, a public copy constructor declaration is still needed, which means, objects of boost::noncopyable is not compatible with NRVO.
std::auto_ptr is another trial to bypass the copy constructor. You might have seen its "copy constructor" implemented like
template <typename _T>
auto_ptr(auto_ptr<_T>& source)
{
_ptr = source._ptr; // where _ptr is the pointer to the contained object
source._ptr = NULL;
}
This is not a copy at all, but a "move". You could consider this kind of behavior as the prototype of a move semantic.
But std::auto_ptr also has its own problem: it is not compatible with STL containers. So, unfortunately, anything about noncopyable is painful.
That was painful until, the C++0x move semantic is finally published and implemented by the compiler makers.
In simple way, you could just think of move semantic as something same as the "copy" behavior of std::auto_ptr, but with a full support by the language features so it works fine with containers and algorithm.
By the way in C++0x the std::auto_ptr is deprecated and a new template type std::unique_ptr is recommended.
My story will end now. Please refer to other posts if you want to know more about it like strange syntax and rvalue system.

Is it bad form to call the default assignment operator from the copy constructor?

Consider a class of which copies need to be made. The vast majority of the data elements in the copy must strictly reflect the original, however there are select few elements whose state is not to be preserved and need to be reinitialized.
Is it bad form to call a default assignment operator from the copy constructor?
The default assignment operator will behave well with Plain Old Data( int,double,char,short) as well user defined classes per their assignment operators. Pointers would need to be treated separately.
One drawback is that this method renders the assignment operator crippled since the extra reinitialization is not performed. It is also not possible to disable the use of the assignment operator thus opening up the option of the user to create a broken class by using the incomplete default assignment operator A obj1,obj2; obj2=obj1; /* Could result is an incorrectly initialized obj2 */ .
It would be good to relax the requirement that to a(orig.a),b(orig.b)... in addition to a(0),b(0) ... must be written. Needing to write all of the initialization twice creates two places for errors and if new variables (say double x,y,z) were to be added to the class, initialization code would need to correctly added in at least 2 places instead of 1.
Is there a better way?
Is there be a better way in C++0x?
class A {
public:
A(): a(0),b(0),c(0),d(0)
A(const A & orig){
*this = orig; /* <----- is this "bad"? */
c = int();
}
public:
int a,b,c,d;
};
A X;
X.a = 123;
X.b = 456;
X.c = 789;
X.d = 987;
A Y(X);
printf("X: %d %d %d %d\n",X.a,X.b,X.c,X.d);
printf("Y: %d %d %d %d\n",Y.a,Y.b,Y.c,Y.d);
Output:
X: 123 456 789 987
Y: 123 456 0 987
Alternative Copy Constructor:
A(const A & orig):a(orig.a),b(orig.b),c(0),d(orig.d){} /* <-- is this "better"? */
As brone points out, you're better off implementing assignment in terms of copy construction. I prefer an alternative idiom to his:
T& T::operator=(T t) {
swap(*this, t);
return *this;
}
It's a bit shorter, and can take advantage of some esoteric language features to improve performance. Like any good piece of C++ code, it also has some subtleties to watch for.
First, the t parameter is intentionally passed by value, so that the copy constructor will be called (most of the time) and we can modify is to our heart's content without affecting the original value. Using const T& would fail to compile, and T& would trigger some surprising behaviour by modifying the assigned-from value.
This technique also requires swap to be specialized for the type in a way that doesn't use the type's assignment operator (as std::swap does), or it will cause an infinite recursion. Be careful of any stray using std::swap or using namespace std, as they will pull std::swap into scope and cause problems if you didn't specialize swap for T. Overload resolution and ADL will ensure the correct version of swap is used if you have defined it.
There are a couple of ways to define swap for a type. The first method uses a swap member function to do the actual work and has a swap specialization that delegates to it, like so:
class T {
public:
// ....
void swap(T&) { ... }
};
void swap(T& a, T& b) { a.swap(b); }
This is pretty common in the standard library; std::vector, for example, has swapping implemented this way. If you have a swap member function you can just call it directly from the assignment operator and avoid any issues with function lookup.
Another way is to declare swap as a friend function and have it do all of the work:
class T {
// ....
friend void swap(T& a, T& b);
};
void swap(T& a, T& b) { ... }
I prefer the second one, as swap() usually isn't an integral part of the class' interface; it seems more appropriate as a free function. It's a matter of taste, however.
Having an optimized swap for a type is a common method of achieving some of the benefits of rvalue references in C++0x, so it's a good idea in general if the class can take advantage of it and you really need the performance.
With your version of the copy constructor the members are first default-constructed and then assigned.
With integral types this doesn't matter, but if you had non-trivial members like std::strings this is unneccessary overhead.
Thus, yes, in general your alternative copy constructor is better, but if you only have integral types as members it doesn't really matter.
Essentially, what you are saying is that you have some members of your class which don't contribute to the identity of the class. As it currently stands you have this expressed by using the assignment operator to copy class members and then resetting those members which shouldn't be copied. This leaves you with an assignment operator that is inconsistent with the copy constructor.
Much better would be to use the copy and swap idiom, and express which members shouldn't be copied in the copy constructor. You still have one place where the "don't copy this member" behaviour is expressed, but now your assignment operator and copy constructor are consistent.
class A
{
public:
A() : a(), b(), c(), d() {}
A(const A& other)
: a(other.a)
, b(other.b)
, c() // c isn't copied!
, d(other.d)
A& operator=(const A& other)
{
A tmp(other); // doesn't copy other.c
swap(tmp);
return *this;
}
void Swap(A& other)
{
using std::swap;
swap(a, other.a);
swap(b, other.b);
swap(c, other.c); // see note
swap(d, other.d);
}
private:
// ...
};
Note: in the swap member function, I have swapped the c member. For the purposes of use in the assignment operator this preserves the behaviour to match that of the copy constructor: it re-initializes the c member. If you leave the swap function public, or provide access to it through a swap free function you should make sure that this behaviour is suitable for other uses of swap.
Personally I think the broken assignment operator is killer. I always say that people should read the documentation and not do anything it tells them not to, but even so it's just too easy to write an assignment without thinking about it, or use a template which requires the type to be assignable. There's a reason for the noncopyable idiom: if operator= isn't going to work, it's just too dangerous to leave it accessible.
If I remember rightly, C++0x will let you do this:
private:
A &operator=(const A &) = default;
Then at least it's only the class itself which can use the broken default assignment operator, and you'd hope that in this restricted context it's easier to be careful.
I would call it bad form, not because you double-assign all your objects, but because in my experience it's often bad form to rely on the default copy constructor / assignment operator for a specific set of functionality. Since these are not in the source anywhere, it's hard to tell that the behavior you want depends on their behavior. For instance, what if someone in a year wants to add a vector of strings to your class? You no longer have the plain old datatypes, and it would be very hard for a maintainer to know that they were breaking things.
I think that, nice as DRY is, creating subtle un-specified requirements is much worse from a maintenance point of view. Even repeating yourself, as bad as that is, is less evil.
I think the better way is not to implement a copy constructor if the behaviour is trivial (in your case it appears to be broken: at least assignment and copying should have similar semantics but your code suggests this won't be so - but then I suppose it is a contrived example). Code that is generated for you cannot be wrong.
If you need to implement those methods, most likely the class could do with a fast swap method and thus be able to reuse the copy constructor to implement the assignment operator.
If you for some reason need to provide a default shallow copy constructor, then C++0X has
X(const X&) = default;
But I don't think there is an idiom for weird semantics. In this case using assignment instead of initialization is cheap (since leaving ints uninitialized doesn't cost anything), so you might just as well do it like this.