JUN
17
2005

Mmmm... C++

(This entry is, of course, in relation to logs by ruurd, ruurd and mpyne)

I find it troubling that after so many years, C++ still needs such detailed explanations about fundamental functionality. If this situation would have taken a few short years, we could have suspected slow social evolution from old (functional languages) to new (OOP) paradigms. But persistence, at the scale we see, seem to rather signal problems with the language itself. The current discussion is just a small sand grain falling in the timeglass that measures the evolution of my personal relationship with C++, from 100% love (1993-4) to fifty/fifty love/hate (2003-4).

My personal opinion is that cctor and op= definition necessity is one of those subtle oddities of the language that grow to irk one's confidence in its tools (in the matter, C++), with the growing of experience. You have to grant it, coding cctor and op= over and over again, a few tens of times per week, becomes irky after 10 years.

ruurd and mpyne showed off the peculiarity of the relationships between the two constructs, using a similar situation as a frame for both (new creation of an object). It is a duty, IMHO, to underline that the op= is essential in all other (other than new creation) situations. This is the fundamental reason of its existence: carbon copying to already existing objects. This is why cctor only wouldn't be enough.

I tend to agree that the language should impose a bit more intelligence on the compilers and require an equivalence of actions between cctor and op=. I didn't yet encounter a situation where different behaviors be required from the two notions. Counterexamples more than welcome.

But from this positioning derives a warning too. Given that the compilers aren't required to ensure equivalence, it is your sacred duty to fulfill this requirement. Thus, advice no. 1: make very sure, always, that equivalent operations are perfomed on the call of any of the two operators and that the result is identical (i.e. a copy-constructed object or an assigned object would look the same).

The second advice is an amendment to Coplien's rule of Big Four. Advice no. 2: make sure to reflect thoroughly at the necessity of providing your class with a cctor and an op=. Act conservatively with respect to this reflection. It is very well known that the compiler provides some default ctor, cctor, op= and dtor if none manually defined. But it is quite often required that an object not be able to become into existence by copy-construction and, similarly, it might be necessary that no assignation be possible to the said object. In case you don't want cctor and/or op= (not even the default ones), it is enough to declare them private and forget to define (implement) them. This will give you nice compile-time errors if you ever try to copy-construct or assign-to such objects.

Again, fundamental knowledge...

Comments

The problem with defining the copy ctor or the operator = with the other is that it would need to change fundamental things in the language.


First, you'll have to define the copy constructor with the operator = since, as you said, in the copy constructor you just built a fresh object and in the operator = you are copying one. The operator = needs to do more things that the copy constructor (mainly, freeing resources). The proposal would be to make the copy construction being equivalent to

   CopiedClass(CopiedClass const& source)
   { *this = source; }

Let's take a look at an example

class Foo
{ 
  Bar* m_bar;
public:
  Foo(Foo const& foo)
  { 
    *this = foo; 
  }
  Foo& operator = (Foo const& foo)
  { 
    if (this == &foo) return;
    delete m_bar;
    m_bar = new Bar(*foo.m_bar);
  }
};

Hell. m_bar is not initialized to 0 by the construction. So, you'll have to change C++ so that construction initializes the PODs. This breaks one of the design mantra of C++: "you don't pay for what you don't use"


In general, you would have to ensure that every operation in operator = can be done on objects freshly built (i.e. not initialized by any object constructors). This can lead to difficulties since the purpose of construction/destruction cycles in C++ is to constraint the state of object members during all the object lifetime.


By the way, if the Foo a = b; construct troubles you (I don't see why, but it is your right to be troubled :D), use explicit. The automatic conversion is one of the basic tools of the language but sometime it should be disabled (one of my friends does the opposite: first, use explicit, then think)


By doudou at Fri, 06/17/2005 - 02:34

No, I'm not keen on blindly defining one with the other. And yes, I understand very well the above considerations.

As I said in the beginning, the fundamental requirements of the language are logically explained, but the fact that these requirements exist spells (to me!) problems with the design and principles of the language itself. But I must acknowledge not being a programming languages expert.


By Cristian Tibirna at Fri, 06/17/2005 - 08:12

The assertion that cctor and op= do the same is just a restriction.

What if you want one of them to be in the public API, but the other one should not be accessible?
Like allowing creation of copies but not allowing state changes during runtime, i.e. having cctor public and op= private?

Or when pure assignment isn't possible due to members only a constructor can initialize like const references?

If one want those two to behave equally, one can always create a private method which performs the copying and call it in both


By krake at Fri, 06/17/2005 - 03:05

> The assertion that cctor and op= do the same is just a
> restriction.

Not "do the same" but "give the same result". There's a nuance.


By Cristian Tibirna at Fri, 06/17/2005 - 08:04