Solved

operators vs functions; return types

Posted on 2004-10-09
15
258 Views
Last Modified: 2013-12-14
I have a class called Vector - an array of doubles which performs lots of math operations (Matlab-style).

Let's take for instance a multiplication operation. It can be done in two ways:

1. As an operator, returning the result as a new object:

Vector Vector::operator *(Vector &v) // multiplies vectors if Lengths match
{
      if (VLength!=v.VLength)
      {
            Vector retvec;
            retvec.matherror = true;
            return retvec;
      }

      Vector retvec(VLength);
      if (retvec.memerror)
            return retvec;

      for (int l=0;l<VLength;l++)
            retvec.data[l] = data[l]*v.data[l];

      return retvec;
}

///////////////////////////

2. As a function, which accepts the "destination" object and changes it accordingly:

void Vector::MultVec(Vector &v, Vector &dest) // multiplies vectors if Lengths match
{
      if (VLength!=v.VLength)
      {
            dest.matherror = true;
            return;
      }

      dest.Resize(VLength);
      if (dest.memerror)
            return;

      for (int l=0;l<VLength;l++)
            dest.data[l] = data[l]*v.data[l];
}

///////////////////////////////////////////////

Now, when I call the first and the second versions, I write:

Vector v1, v2;
Vector v3, v4;
// ...
// Above, something is done with v1 and v2
v3 = v1 * v2;
v1.MultVec(v2, v4);

In the first case, "retvec" is created inside the operator and then copied into v3 upon returning.
In the second case, v4 is resized and filled with data.

While the first case is much more readable, obviously, the second case is more efficient when long vectors are concerned (no copying!).

Now, for my question: is there a way to avoid copying and still use the operators?

What I do _not_ want to do is allocate a dynamic object inside the operator and then return a pointer. This I won't accept as an answer! Any other suggestions? Is it maybe possible to do something like "assign output argument value"?

Thanks!
0
Comment
Question by:Lescha
  • 10
  • 5
15 Comments
 
LVL 17

Accepted Solution

by:
rstaveley earned 50 total points
ID: 12266893
Most compilers will apply return value optimisation to Vector Vector::operator *(Vector &v), thus avoiding the copy.
0
 
LVL 1

Author Comment

by:Lescha
ID: 12267538
How can I test for this? Read assembly for debug/release, or is there a better way?
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 12267621
You can generate assembly listing with most compilers and that's generally reasonably readable, because it puts the C++ source code into it too.

e.g.

Visual C use the /Fa switch
GNU use the -S switch
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 17

Expert Comment

by:rstaveley
ID: 12267755
The following is educational.

--------8<--------
#include <iostream>
using namespace std;

struct foo {
      foo() {cout << "foo default ctor\n";}
      foo(const foo&) {cout << "foo copy ctor\n";}
      ~foo() {cout << "foo dtor\n";}
      foo& operator=(const foo&) {cout << "foo assignment\n";return *this;}
};

foo f()
{
      foo a;
      return a;
}

int main()
{
      cout << "(1)\n";
      foo b = f();
      cout << "(2)\n";
      foo c;
      c = f();
      cout << "(3)\n";
}
--------8<--------

With Visual C, you'll see:

--------8<--------
(1)
foo default ctor         <- a is constructed using the default ctor in f()
foo copy ctor            <- b is constructed using a copy constructor directly from a
foo dtor                   <- now we leave the scope of f() and get a's dtor
(2)
foo default ctor         <- c is constructed using the default ctor
foo default ctor         <- a is constructed using the default ctor in f()
foo copy ctor            <- here we have the return value temporary object ctor copy constructed from a
foo dtor                   <- now we leave the scope of f() and get a's dtor
foo assignment         <- c is assigned from the return value temporary object
foo dtor                    <- return value temporary object's dtor
(3)
foo dtor                   <- b's dtor
foo dtor                   <- c's dtor
--------8<--------

Change the code to:

--------8<--------
#include <iostream>
using namespace std;

struct foo {
      foo() {cout << "foo default ctor\n";}
      foo(const foo&) {cout << "foo copy ctor\n";}
      ~foo() {cout << "foo dtor\n";}
      foo& operator=(const foo&) {cout << "foo assignment\n";return *this;}
};

foo f()
{
      return foo();
}

int main()
{
      cout << "(1)\n";
      foo b = f();
      cout << "(2)\n";
      foo c;
      c = f();
      cout << "(3)\n";
}
--------8<--------

...and you get....

--------8<--------
(1)
foo default ctor         <- a is constructed directly using the default ctor in f()
(2)
foo default ctor         <- c is constructed using the default ctor
foo default ctor         <- here we have the return value temporary object default ctor
foo assignment         <- c is assigned from the return value temporary object
foo dtor                    <- return value temporary object's dtor
(3)
foo dtor                   <- b's dtor
foo dtor                   <- c's dtor
--------8<--------

I'd expect similar optimisation with GNU's compiler with or without optimisation specified.
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 12267766
Comment error above. That should have read...

...and you get....

--------8<--------
(1)
foo default ctor         <- *b* is constructed directly using the default ctor in f()
(2)
foo default ctor         <- c is constructed using the default ctor
foo default ctor         <- here we have the return value temporary object default ctor
foo assignment         <- c is assigned from the return value temporary object
foo dtor                    <- return value temporary object's dtor
(3)
foo dtor                   <- b's dtor
foo dtor                   <- c's dtor
--------8<--------

I'm sure you guessed as much anyhow.
0
 
LVL 1

Author Comment

by:Lescha
ID: 12267807
Yeah, I knew that stuff.
My problem is that I work in parallel with C and with Matlab, and I'd kill to have some of Matlab's "tricks"! For instance, there are such things as "output arguments" which can be written to directly. Of course, it is possible that this is a "fool the user" case, and the copying of temporary values is simply done "behind the curtains", but what if?..

I'll check out the assembly code and get back to you...
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 12267857
You'll get better optimisation, if you had suitable ctors, which allowed you to write something like this:

--------8<--------
Vector Vector::operator *(Vector &v) // multiplies vectors if Lengths match
{
     if (VLength!=v.VLength)
     {
          return Vector(Vector::MathError); // Ctor sets matherror = true
     }
     return Vector(VLength,i,data[l]*v.data[l]); // Ctor does the data assignment
}
--------8<--------

The reason is explained in efn's excerpt from the standard at http:/Q_21162129.html#12267671 which oddly enough is addressing a similar matter to yours simultaneously. By using an automatic object, the compiler is allowed to construct the Vector object directly into v3, if you have...
   
   Vector v3 = v1 * v2;
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 12268369
FWIW... efn just put me right in the other thread about this not being an automatic object. What I ought to have said is an unnamed object (i.e. a temporary).

<FX: rstaveley disappears into the bottom of his glass of beer>
0
 
LVL 1

Author Comment

by:Lescha
ID: 12269045
Could you please elaborate a bit on that? I can understand what you mean by having a special constructor for matherror, but this is not my problem. What did you mean by

return Vector(VLength,i,data[l]*v.data[l]); // Ctor does the data assignment

???

If you meant "return a vector of constant values and a given length", I have that kind of constructor, but that's not what I need. Otherwise, I don't quite get that idea...
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 12269498
I meant to have a constructor that does all of this in one hit:

--------8<--------
    Vector retvec(VLength);
     if (retvec.memerror)
          return retvec;

     for (int l=0;l<VLength;l++)
          retvec.data[l] = data[l]*v.data[l];
--------8<--------

i.e. get the data initialisation into the ctor.
0
 
LVL 1

Author Comment

by:Lescha
ID: 12269506
Economical, but unpractical, seeing as how I have several tens, if not hundreds of different operators with different types of input parameters.

If you're correct about the optimization, however, it won't matter much.

Are you sure there is no way of directly accessing the output parameters of a function?
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 12269723
It is tempting to do something like passing an object by reference and returning that object by reference. A good example of this approach is implementation of...

   ostream& operator<<(ostream& os,const MyClass& myObject)
   {
         // os << myObject.blah();
         return os;
   }

But this can't be put into practice for the expression...

   v3 = v1 * v2;

...because you've got an assignment operator and a multiplication operator. You need something to take the result.

operator*() puts the result into a return value temporary, which is used for the copy constructor for...

     Vector v3 = v1 * v2; // Equivalent to Vector v3(v1 * v2) in any good compiler
                                    // i.e. Uses copy ctor from temporary

...or the assignment operator for...

     Vector v3; // Default ctor
     v3 = v1 * v2; // Assignment

Clearly return value optimisation is good for...

     Vector v3 = v1 * v2;

...because a smart compiler is smart enough to use it directly in the copy ctor for v3, but with assignments like...

     v3 = v1 * v2; // Assignment

... the return value isn't the end of the line. The temporary object needs to be passed as a parameter to the assignment operator function, which does more than just copy construction.

You'll often find operator*() implemented in terms of operator=*(), using a local temporary (automatic variable!). operator*=() can return a reference to *this, because it is a non-const operation. Unlike your code, operator*() should really be const for the lhs and the rhs, and is usually declared in the global namespace as a binary function rather than the class unary function.

e.g. Consider this trivial example, with 2D coords:
--------8<--------
#include <iostream>
using namespace std;

struct Coord {
        int x,y;
        Coord(int x,int y) : x(x),y(y) {}
        Coord& operator*=(const Coord& rhs) {
              x *= rhs.x;y *= rhs.y;
              return *this;
        }
};

Coord operator*(const Coord& lhs,const Coord rhs)
{
      return Coord(lhs) *= rhs;
}

ostream& operator<<(ostream& os,const Coord& coord)
{
      return os << '(' << coord.x << ',' << coord.y << ')';
}

int main()
{
      Coord a(2,3);
      Coord b(4,5);
      cout << "Product is " << a*b << '\n';
}
--------8<--------

operator*() needs to return by value rather than by reference. It needs to put its result somewhere.

My two bits... I'd leave optimisation to the compiler and only work at it if the profiler shows that you are bottle-necking because of class copying in your computation. Also, do consider using operator*=() when you don't need to keep the LHS. Also, do use those consts... they will pay you back. Also, do try to avoid constructing objects before they are needed (the copy constructor is less expensive than assignment).
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 12269732
Where I've written...

> operator=*()

... I meant of course operator*=()
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 12269846
However... the one bit of assistance for optimisation which would provide good payback would be to inline your operator*() function and implement it in the header.

i.e.

   inline Vector operator*(const Vector& lhs,const Vector& rhs) {
         return Vector(lhs) *= rhs;
   }

lhs and rhs should be const references too. I missed the '&' in my example.
0
 
LVL 1

Author Comment

by:Lescha
ID: 12271610
Yeah, okay. Thanks. I'll try to go on from here.
Maybe I really should've taken that C++ course instead of picking up things here and there - sometimes it's good to have a large input all at once...
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

IntroductionThis article is the second in a three part article series on the Visual Studio 2008 Debugger.  It provides tips in setting and using breakpoints. If not familiar with this debugger, you can find a basic introduction in the EE article loc…
Go is an acronym of golang, is a programming language developed Google in 2007. Go is a new language that is mostly in the C family, with significant input from Pascal/Modula/Oberon family. Hence Go arisen as low-level language with fast compilation…
The viewer will learn how to use NetBeans IDE 8.0 for Windows to connect to a MySQL database. Open Services Panel: Create a new connection using New Connection Wizard: Create a test database called eetutorial: Create a new test tabel called ee…
The viewer will be introduced to the member functions push_back and pop_back of the vector class. The video will teach the difference between the two as well as how to use each one along with its functionality.

679 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question