Hi all,
The default copy constructor just caused me a lot of grief. I had written a template class which used the operator "=" for assignment (I was using both complicated objects and basic data types).
I had forgotten to overload the "=" operator for one of the complicated objects. The Compiler had no problem with that - it maliciously called the copy constructor (issuing no warning whatsoever), which made objects share pointers and so the program crashed after 10 minutes of computation in an impossible to debug situation.
The resulting headbanging lost me two days of work.
How can this be avoided? Is there a way to tell the compiler to never use default copy constructors unless specifically instructed to do so? How do you deal with such problems?
copy ctor as well as the = operator should always be declared for complex objects. If you do not want these to exist, then make them private in the class, and don't give them bodies:
1 2 3 4 5 6 7 8 9
class C
{
public:
// stuff
private:
C(const C&); // do not give these a body
C& operator = (const C&);
};
Doing this will throw a compiler error if anywhere outside your class attempts to copy the class because they're private. Also, if they don't have a body, then you'll get a linker error if you accidentally try to copy an object inside your class.
I just got a compiler warning, more than one copy constructor available! Now the thing is, I *do not* want copy constructors to be *ever* used! What should I do?
1 2 3 4 5 6 7 8 9 10
class Rational
{
public:
Rational(){};
private:
//the below operators/constructors are never given a body
voidoperator=(Rational&);
voidoperator==(Rational&);
Rational(const Rational&);
};
Sorry for the post, just found my error! anyways, the P.S. is still valid :)
P.S. If I had to choose two things to throw out of c++, I would start with 1) eliminate default copy constructors. 2) I will forbid expressions like a=b=c;. In case you accidentally write if(a=b) instead of if(a==b), the compiler should refuse to compile - it doesn't at the moment since the operator "=" does return a value - a big design flaw in c++.
0) no need to overload ==, that has no default behavior. Also you should change operator = to accept const Rational&.
1) It's likely a carry over from C. I'd agree, actually... at least somewhat. Default copy ctors (and default assignment operators) should exist for structs but not for classes. For classes they generally cause more problems than they solve.
2) Lots of people (including myself) would disagree with you on this point. Mixing up = and == is something you only do for so long -- after a while not only do you stop making that mistake, but you can spot immediately when somebody else makes that mistake in their code (and if you do make the mistake in your code -- you can spot it right away by its behavior). I occasionally (but not very often) rely on a=b to return a value, and wouldn't like that functionality removed.
Although... it is a common problem. I think a better solution to address this problem would have been to use the delphi-style assignment operator. a := b; instead of a = b;. Another way to sort of get around this problem (which Java employed) is to only allow conditionals to input strict bools (so if(a=b) would be an error unless 'a' is a bool), but I dislike that approach.
edit -
jsmith: surely string (and every other complex stl class) overloads the copy ctor. There's no way it uses the default.
98% of the time the default copy constructor works for the classes/structs I write. I only need to write a custom copy constructor for classes in which member-wise copy is not sufficient (ie, for taking 'deep' copies of pointers). Since holding raw pointers in classes/structs presents its own set of challenges (who owns the pointer? what if someone else has a copy of the pointer? memory leaks, etc), a better design is to use std::auto_ptr (which makes the class non-copyable by default) or boost smart pointers (which makes the default copy constructor work just fine).
Well, here are my reasons against default copy constructors. I never use
void foo( std::string s ); - to me it is much better
void foo( std::string& s ); - you are passing only 4 bytes of information this way - I think it's way more efficient. As far as the danger that foo modifies s passed to it - that is why there are private/public/friend identifiers.
I am not saying eliminate automatic copy constructors - just take away their *default* status. Make them available only through *explicit* programmer call (for example, by introducing a new keyword, say "default_copy").
As far as the if(a=b) warning goes: I wrote
1 2 3
std::string tempS;
/*...*/
if(tempS[0]='+'){}
and the compiler didn't issue a warning.
Finding that mistake (it popped in a really obscure place in my computations) took me 45 minutes. Now, saving 15 seconds at a time by using syntax like if (tempBool=b()), I will need to use it 180 times to make up for my lost time (provided I never make the mistake again!) :)
Found the option! Now it warns me! One must set "warning level" to "level 4" (it used to be at "level 3"). The default is level 3 (I never changed it since the original installation).
After compiling with the new warning settings only warnings I got were that I declare/initialize variables but never use them. *smiles delighted* (this on 7k+ lines of code :))
Thanks a lot Disch!
Follow up:
What I needed is warning level 4 with the following two lines of code:
1 2 3 4
#pragma warning(disable:4100)
//warning C4100: non-referenced formal parameter
#pragma warning(disable:4189)
//warning C4189: variable initialized but never used