tags:

views:

4837

answers:

9

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?

+2  A: 

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?!

graham.reeds
+35  A: 

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.

Mark Ingram
That problem doesn't stop it from generating a copy ctor, where it's quite harmful.
MSalters
Copy constructors are used in an entirely different context than comparison operators. And, IMHO, its context is clear in what it is doing.
spoulson
There is the problem of compatibility with C: C89 produces code for structs that mimicks C++'s assignement (and probably copy constructors... I have to check). Thus, it is normal C++ generates similar codes.
paercebal
Copy constructors (and `operator=`) generally work in the same context as comparison operators - that is, there is an expectation that after you perform `a = b`, `a == b` is true. It definitely makes sense for the compiler to provide a default `operator==` using the same aggregate value semantics as it does for `operator=`. I suspect paercebal is actually correct here in that `operator=` (and copy ctor) are provided solely for C compatibility, and they didn't want to make situation any worse.
Pavel Minaev
Viktor Sehr
Viktor, I suggest you re-think your response. If the class Foo contains a Bar*, then how would the compiler know whether Foo::operator== wants to compare the address of Bar*, or the contents of Bar?
Mark Ingram
+7  A: 

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.

MSalters
I thought that only the "special member functions" (default constructor,copy constructor, assignment operator and destructor) could be explicitly-defaulted. Have they extended this to some other operators?
Michael Burr
Move constructor can also be defaulted, but I don't think this applies to `operator==`. Which is a pity.
Pavel Minaev
+6  A: 

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.

Paul de Vrieze
There is no ambiguity for POD structs - they should behave in exact same way any other POD type does, which is value equality (rather then reference equality). One `int` created via copy ctor from another is equal to the one from which it was created; the only logical thing to do for a `struct` of two `int` fields is to work in exact same way.
Pavel Minaev
Well, but what if the POD struct has a value whose relevance is dependent on another value. In the case that the value is not relevant, it's no longer bit-for-bit equality. Equality can really range between one and the same object (only with pointers/references) to anything somewhat the save (perhaps of a subclass)
Paul de Vrieze
+1  A: 

The default comparison operators would be correct a vanishingly small amount of the time; I expect that they would be a source of problems rather than something useful.

Also, the default methods you mention are often undesirable. Seeing code like this to get rid of the default copy constructor and operator= is very common:

class NonAssignable {
// ....
private:
    NonAssignable(const NonAssignable&);  // Unimplemented
    NonAssignable& operator=(const NonAssignable&);  // Unimplemented
};

In a lot of code it is common to see a comment "default copy constructor and operator= OK" to indicate that it is not a mistake that they have been removed or explicitly defined.

janm
Inheriting from boost::noncopyable is another very good way of supressing stray copy constructors and assignment operators.
CesarB
Yes, that is true. In fact, it is so common that it made it into boost and is regularly used.
janm
+6  A: 

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)

sergdev
+23  A: 

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 to not 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 in 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.

Michael Burr
+2  A: 

Just a note, also provided by the compiler for free:

  • operator new
  • operator new[]
  • operator delete
  • operator delete[]
Jeffrey Martinez
+6  A: 

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 boost::shared_ptr), the default copy constructor is usually fine and the obvious implementation of the hypothetical default comparison operator would be as fine.

alexk7
By the way, I am designing a programming language that will improve the "value-based" semantics from their experimental state in C++ to their full completion. My wife is helping me spread the word: www.c3wife.com
alexk7