Solved

operators vs functions; return types

Posted on 2004-10-09
15
246 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
 
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
Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

 
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

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Templates For Beginners Or How To Encourage The Compiler To Work For You Introduction This tutorial is targeted at the reader who is, perhaps, familiar with the basics of C++ but would prefer a little slower introduction to the more ad…
Container Orchestration platforms empower organizations to scale their apps at an exceptional rate. This is the reason numerous innovation-driven companies are moving apps to an appropriated datacenter wide platform that empowers them to scale at a …
This tutorial covers a step-by-step guide to install VisualVM launcher in eclipse.
The goal of the video will be to teach the user the concept of local variables and scope. An example of a locally defined variable will be given as well as an explanation of what scope is in C++. The local variable and concept of scope will be relat…

706 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

Need Help in Real-Time?

Connect with top rated Experts

15 Experts available now in Live!

Get 1:1 Help Now