# operators vs functions; return types

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!
LVL 1
###### Who is Participating?

x

Commented:
Most compilers will apply return value optimisation to Vector Vector::operator *(Vector &v), thus avoiding the copy.
0

Author Commented:
How can I test for this? Read assembly for debug/release, or is there a better way?
0

Commented:
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

Commented:
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

Commented:
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

Author Commented:
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

Commented:
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

Commented:
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

Author Commented:
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

Commented:
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

Author Commented:
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

Commented:
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

Commented:
Where I've written...

> operator=*()

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

Commented:
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

Author Commented:
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
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.