is there any better way of writing operators in c++?

Started by
31 comments, last by Deyja 17 years, 9 months ago
If your operator has a chance of leaving your object in an invalid state, you have a problem, and simply not using it in this one case isn't a very good solution.

Besides, consider why it might throw an exception. Simple addition is unlikely to throw an exception, so presumably it dies for memory related reasons. If this is the case, your assignment operator might throw an exception half way through while copying, and your problem still exists, you haven't fixed it!

I think + should be implemented in terms of +=, return Foo(*this)+=whatever;.
Chess is played by three people. Two people play the game; the third provides moral support for the pawns. The object of the game is to kill your opponent by flinging captured pieces at his head. Since the only piece that can be killed is a pawn, the two armies agree to meet in a pawn-infested area (or even a pawn shop) and kill as many pawns as possible in the crossfire. If the game goes on for an hour, one player may legally attempt to gouge out the other player's eyes with his King.
Advertisement
Exception Safe Case:
MyInt operator +( const MyInt lhs, const MyInt rhs )
{
MyInt temp( lhs );
temp.m_Value += rhs.m_Value;
return temp;
}

MyInt & operator +=( const MyInt rhs )
{
MyInt temp( m_Value + rhs.m_Value );
Swap( temp );
return * this;
}

void MyInt::Swap( MyInt & rhs )
{
std::swap( m_Value, rhs.m_Value );
}

In this situation you will only run out of memory in the allocation of the temps. And if you ever run out of memory, your origional object is still in a valid state. So contrary to what you said, there isn't a problem. The problem is fixed. That's the whole point of exception safety.

Also you could use Swap( temp ); in many other places, like operator =. This helps you work on a seperate, temporary copy, preserving your origional state. You can read more about it in Exceptional C++.
Also it is perfectly common for non-exception safe operators to leave an object in an invalid state. Something so simple as MyInt won't have a problem since its only operations are atomic. But take a class that is more complex:

Matrix & Matrix::operator *=( const Matrix & rhs )
{
// This is not atomic.
// If an error occurs half way through, our object is out of a valid state.
}

Same with vectors. Same with all of the std container library.

Suppose you are calling operator = on a container, which is templated over type T. You have no garuntee that T's operator = won't throw an exception.

And this is good because maybe T is something like database connections, where you may want only 4 max. Copying and trying to create 8 would be bad. T's operator = should throw in this case.

But that means your container's operator = can throw an exception half way through. If your origional had 3 connections, the first connection will copy without throwing. But then the second connection will throw. And now your container is in an invalid state.
can any one rewrite my code and show me my mistakes?
Perhaps our idea of an 'invalid state' differs, because the container will still contain all the objects upto the one that threw the exception, and be just as usable as any other container.
Chess is played by three people. Two people play the game; the third provides moral support for the pawns. The object of the game is to kill your opponent by flinging captured pieces at his head. Since the only piece that can be killed is a pawn, the two armies agree to meet in a pawn-infested area (or even a pawn shop) and kill as many pawns as possible in the crossfire. If the game goes on for an hour, one player may legally attempt to gouge out the other player's eyes with his King.
I don't think it will be just as usable. First off a half-filled container when you expected a full container is already wrong. Second the copy will likely look like this:

m_Count = rhs.m_Count;
for( unsigned int i( 0 ); i < m_Count; ++i )
{
// copy item i
}

If this fails half way through, your count still shows an incorrect number. This is why NONE of the standard containers do this. It isn't exception safe. It doesn't "still work."
You're saying that if operator += throws an exception, the object will be in an invalid state, and hence using the temporary fixes this.

I'm saying it doesn't because if operator += can can throw an exception, then operator = can also throw an exception, and that x=x+y could be just as wrong x+=y.

If operator = is sane enough not to leave an object in an invalid state, then there is absolutely no reason why operator += can't be sane enough to do the same. If you're going to have operator += to throw an exception, there's no reason why you can't do it before you've changed anything, or if you do change something, keep your object in an internally consistant state.
Chess is played by three people. Two people play the game; the third provides moral support for the pawns. The object of the game is to kill your opponent by flinging captured pieces at his head. Since the only piece that can be killed is a pawn, the two armies agree to meet in a pawn-infested area (or even a pawn shop) and kill as many pawns as possible in the crossfire. If the game goes on for an hour, one player may legally attempt to gouge out the other player's eyes with his King.
Quote:Original post by clearly
can any one rewrite my code and show me my mistakes?


Here ya go:

// NOOOOOOO!!!!!!  <iostream.h> is NOT part of the C++ standard// and provides DIFFERENT TYPES to that specified by the standard// (and on my copy of VS2005 doesn't even exist).  Standard C++// headers drop the .h off the end of the filename.#include <iostream>// Don't be afraid of writing small functions inlined within the class.class MyInt{public:    MyInt() : value(0)    {    }    // Use explicit if you don't want the compiler to automatically convert    // from an int to a MyInt.  Not strictly necessary here and perhaps not    // even desired, but it can become an issue if you have multiple implicit    // casts that would allow the compiler to find strange ways of casting    // from one type to another that you're not expecting.    explicit MyInt(int x) : value(x)    {    }     // If your getter is doing nothing but return an exact copy    // of an internal variable, return a const reference as it    // reduces the number of temporaries created.  Of course, with    // a plain old int this won't make any difference whatsoever.    const int& GetValue() const    {        return value;    }    // Assignment operators should always return a reference to the    // object being assigned, not a copy of it.  Some people prefer    // to return a const reference to prevent this: (a = b) = c, but    // this is a personnal choice and the behaviour should be clearly    // stated in any documentation as it differs from the norm.    MyInt& operator=(const MyInt& rhs)    {        // Pointless optimisation!!  It'll take longer to get the        // value and do the comparison than it would to just assign it.        //if(value==rhs.GetValue())        //    return *this;        // If your doing something a bit more interesting and *really*        // need to do this sort of check, it'd be better to do it like this:        //int rhsValue = rhs.GetValue(); // Cache the result locally        //if(value!=rhsValue) value = rhsValue;             // Calling GetValue() isn't really necessary in this case, you        // can access rhs.value directly.  It doesn't hurt though and can        // be a good thing if plan on manipulating the value in some        // way before using it (eg. clipping to a range of values).        value = rhs.GetValue();        return *this;    }    MyInt& operator+=(const MyInt& rhs)    {        value += rhs.GetValue();        return *this;    }    MyInt& operator-=(const MyInt& rhs)    {        value += rhs.GetValue();        return *this;    }     // Pre-increment: Increment the value first and then return self    MyInt& MyInt::operator++()    {        ++value;        return *this;    }        // Post-increment: Make a copy of the value first, increment self    // and then return the COPY.    MyInt MyInt::operator++(int a)    {        MyInt old(*this);        ++value;        return old;    }     private:    int value;};// operator+() uses 2 objects to create a third, and// therefore doesn't belong to any single object.// Hence it's kept external to the class.MyInt operator+(const MyInt &lhs, const MyInt &rhs){    // Code reuse is fun!!    return MyInt(lhs) += rhs;}// Another good reason to keep stuff like operator+()// outside of the class.  You wouldn't be able to do// this otherwise (ie. not having MyInt on the LHS)MyInt operator+(int lhs, const MyInt &rhs){    return MyInt(lhs) += rhs;}// If we provide both this and the above overload// of operator+() you can keep the cast constructor// above as explicit (which is a good thing)MyInt operator+(const MyInt &lhs, int rhs){    return MyInt(lhs) += MyInt(rhs);}// Play nice with iostreams!std::ostream& operator<<(std::ostream& out, const MyInt& rhs){    return out << rhs.GetValue();}int main(){    // There's no such thing as cout or endl, this is part of why you    // shouldn't use <iostream.h>.  It's std::cout and std::endl.    // If you want the short-hand versions use the 'using' keyword.    MyInt a(10000);    std::cout << a.GetValue() << std::endl;    // No need for a.GetValue() if we provide an overload    // of operator<<() that works with iostreams.    MyInt b = a + 100;    std::cout << b << std::endl;    MyInt c = a + b;    std::cout << c << std::endl;     a += c;    std::cout << a << std::endl;     a -= c;    std::cout << a << std::endl;    return 0;}

"Voilà! In view, a humble vaudevillian veteran, cast vicariously as both victim and villain by the vicissitudes of Fate. This visage, no mere veneer of vanity, is a vestige of the vox populi, now vacant, vanished. However, this valorous visitation of a bygone vexation stands vivified, and has vowed to vanquish these venal and virulent vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The only verdict is vengeance; a vendetta held as a votive, not in vain, for the value and veracity of such shall one day vindicate the vigilant and the virtuous. Verily, this vichyssoise of verbiage veers most verbose, so let me simply add that it's my very good honor to meet you and you may call me V.".....V
Quote:Original post by joanusdmentia
Quote:Original post by clearly
can any one rewrite my code and show me my mistakes?


Here ya go:

*** Source Snippet Removed ***


thank you very much..
you learned me alot, but i got few things that im not totally understand
like:
    explicit MyInt(int x) : value(x)    {    }

what does the explicit do, and how does it help me?

and when does this happan:
MyInt operator+(const MyInt &lhs, const MyInt &rhs){    // Code reuse is fun!!    return MyInt(lhs) += rhs;}

and about the others..
MyInt& operator+=(const MyInt& rhs)

cant i just write "MyInt" instade of "MyInt&" ?
what is the diffrent?
and why does the operator+ has to be out of the "class members"?
Quote:Original post by clearly
what does the explicit do, and how does it help me?


When you have a constructor of the form MyType::MyType(OtherType val) the compiler will use it to perform a cast from OtherType to MyType. The default behaviour of the compiler is to implicitly perform this cast whenever it sees the need to (eg. when passing an object of OtherType to a function that takes a MyType), but by specifying the explicit keyword you're telling the compiler "only use this constructor when I explicitly ask for it".

Implicit casts can cause problems because the compiler will use as many casts as necessary to try and get from one type to another, so if it can find a series of 10 different implicit casts to get from type A to type B it'll use them and you won't know anything about it. By declaring your casts as explicit you always know when the cast is being used.

Quote:Original post by clearly
and when does this happan:
*** Source Snippet Removed ***


The global operator+(const MyInt&, const MyInt&) will be called when any 2 objects of type MyInt are added together. The other 2 operator+ overloads will be called when a MyInt is added to an int or vice-versa.

Quote:Original post by clearly
and about the others..
MyInt& operator+=(const MyInt& rhs)

cant i just write "MyInt" instade of "MyInt&" ?
what is the diffrent?


Big difference [smile] MyInt& is a reference to a MyInt. References work internally much like pointers do and for simplicity you can think of them like pointers than have non-pointer syntax, but there are some important differences. For instance a reference will always refer to something, you can't have a 'null' reference like you can with pointers.

Quote:Original post by clearly
and why does the operator+ has to be out of the "class members"?


It doesn't have to be, but it's generally advised to be for the reasons I put in the comments (amongst other reasons).
"Voilà! In view, a humble vaudevillian veteran, cast vicariously as both victim and villain by the vicissitudes of Fate. This visage, no mere veneer of vanity, is a vestige of the vox populi, now vacant, vanished. However, this valorous visitation of a bygone vexation stands vivified, and has vowed to vanquish these venal and virulent vermin vanguarding vice and vouchsafing the violently vicious and voracious violation of volition. The only verdict is vengeance; a vendetta held as a votive, not in vain, for the value and veracity of such shall one day vindicate the vigilant and the virtuous. Verily, this vichyssoise of verbiage veers most verbose, so let me simply add that it's my very good honor to meet you and you may call me V.".....V

This topic is closed to new replies.

Advertisement