0

Suppose I have variables passed to a function, say func2, by const reference. Subject to some test, I would like to modify its arguments before calling it. Here is a pseudo-code explaining my goal.

struct strA
{ 
    int a;
    VectorXd v;
};

strA func2(const int& a, const VectorXd& v){...};
strA func1(...){...};

int main(){

    int a;
    VectorXd v;
    bool test;

    if(test){ 
       strA B;
       // some amount of work is required to get the members of B
       B = func1(...);
       func2(B.a, B.v); 

    } else {
       func2(a, v);
    };

    return 0;
}

This code works fine but for readability and genericity, I am wondering whether a better solution exists. Here is an alternative

strA func3(const int& a, const VectorXd& v, test)
{ 
    strA B;
    if(test){ // some amount of work is required to get the members of B
        B = func1(...);

    } else { // map the arguments to a structure
        B.a = a; // the original arguments passed by constant reference
        B.v = v; 
    };

    return B;
}; 

int main(){

    int a;  
    VectorXd v;
    bool test;
    strA B;
    B = func3(a,v,test);
    func2(B.a, B.v);

    return 0; 
}

In the case where test==true (obviously) no problem for keeping the structure B and the arguments a and v as those entities are needed. However, when test==false the alternative is just copying in a structure B the original arguments a and v (which are several large vectors in my application). Is there a cleaner approach or should I stick with the first? By the way, I am bothering with this as this piece of code appears in a loop, so I prefer avoiding copying the same code in several places.

Thank you!

Yuriy Ivaskevych
  • 956
  • 8
  • 18
itQ
  • 59
  • 3
  • 10
  • As a workaround you can create object of `strA` on heap and return a pointer. – Yuriy Ivaskevych Feb 10 '17 at 17:29
  • It's not clear from your question what are you trying to solve. What are your constraints? Is func2 a function you can change? – user1708860 Feb 10 '17 at 17:34
  • @YuriyIvaskevych Thank you for your comment, but can you elaborate please? As I see your suggestion, func3 would return potentially two different things (either a copy of a structure, so I suppose you suggest here a pointer, or a new structure), I don't see what you mean. – itQ Feb 10 '17 at 17:45
  • @user1708860 I am looking for a better solution than the first. The alternative would be good if I can avoid the copy.There are no special constraints. func2 is fixed, you can do whatever you want with func1 and func3. – itQ Feb 10 '17 at 17:49
  • @itQ I may be missing something, but j can't understand what's the problem the first solution is solving. Can you show the code (with the loop) that's the problem? Also, I would recommend against anything that requires you to do dynamic allocations – user1708860 Feb 10 '17 at 18:39
  • @user1708860 Actually there is no problem. The first code is clear if there are only few lines as I gave here, but in my application, I would like to avoid rewriting the if else statement at several places because they repeat the same thing. So, I prefer the alternative (the second code) which is more compact for maintenance. The problem is that it makes unnecessary copies. So the question is : how to rewrite func3 without copying its arguments if they are not modified (given that func2 and the variables a and v do not change as they are needed through the full code)? – itQ Feb 10 '17 at 19:06
  • @user1708860 The conceptual idea is that func3 should return references on a and v if test==false and the new structure B if test==true – itQ Feb 10 '17 at 19:11

2 Answers2

0

I would suggest two approaches:

First: Let's create object of class strA on the heap inside the func3 and return a pointer to that object (e.g. strA* func3(..) instead of copy strA func3(..)):

strA* func3(..) {
    strA* B = new strA;
    ...   // change B.someMember to B->someMember everywhere
    return B;
}

And then use as folows:

strA* B;
...
B = func3(..);
...
delete B;

Caveat: you would have to manage the lifetime of the object yourself, explicitly releasing memory after usage (call delete). However take a look at std::unique_ptr and a few other smart pointers (if you can use c++11).

Second: pass a reference of the object to the function, modify it there and return void:

void func3(/*previous arguments*/, strA* pobj) {
          // nothing to allocate
    ...   // change B.someMember to pobj->someMember everywhere
          // nothing to return as we've modified an object
}

And use:

strA B;
...
func3(..., &B);

Note: however your func1 still returns a copy so you can modify it to or leave as it is.

Yuriy Ivaskevych
  • 956
  • 8
  • 18
0

The two options you presented have an identical amount of copies. After reading your comments, this is what I could gather:

Differentiate between Initialization and Copy constructor/operator =

Let's look at this statement:

strA B;

That line initializes a local variable of type strA with the name B.

Now let's look at this statement:

strA B = foo(3, 4);

Now, this line initializes a local variable of type strA with the name B from a copy of a temporary object returned from foo.

The compiler have a special optimizations just for that called Copy Ellision and Return Value Optimization

So, the second line may be less optimal, but... not really. In the first line, the object is not initialized with the proper data, it uses the default constructor. So what youa re doing in your solutions looks like this:

strA B;
B = foo(3,4);
  1. In this solution, just like the first line, the object is initialized using default ctor;
  2. A temporary object is returned from foo
  3. The object is copied into B using the default operator=

This description varies again based on optimizations. Also, if you are using c++11, it get's more complicated due to Move Semantics

This suffers from both worlds, not only it may call the operator=, it is also using the default constructor. But the code is less readable and makes it harder for the compiler to optimize.

I'm not sure which copy you are trying to prevent. But my suggestion is this -

Don't try to optimize the code before you measured it

You may think that your code will be slow, but you can't really know that. The compiler does practically "dark magic" trying to optimize your code to the maximum level (make sure you are running with -O3 flag on gcc)

Some code

Since I'm still not sure what is it you are trying to fix, I'll just give you some pointers that may (and may not) be helpful. But first, the code:

void calc_and_call(const int& a, const VectorXd& v)
{
    const strA B = func1(a, b);
    func2(b.A, b.B);
}

void func2_wrapper(const int& a, const VectorXd& v, test)
{ 
    if(test)
        calc_and_call(a, v);
    else
        func2(a, v);
};
  • Always Initialize and avoid operator=. This can be generalized further if I told you to always use const

  • Keep object's scope as small as possible (here it's two lines)

Community
  • 1
  • 1
user1708860
  • 1,683
  • 13
  • 32
  • thanks for your attempt. There seems to be a misunderstanding. My first solution makes less copies than the second as the second returns a copy of a and v in a structure when test == false, whereas the first leaves them as they are. Indeed, after func2(...), the second solution is just duplicating a and v. As I said, I prefer the second because it groups the if else statement in a function... – itQ Feb 13 '17 at 07:51
  • But the problem is exactly the duplication of a and v when test == false. – itQ Feb 13 '17 at 07:52
  • @itQ, in the solution I'm offering there are no copies made when the flag is false. – user1708860 Feb 13 '17 at 08:38
  • thanks again, but there is no conceptual difference between my 1st code and what you are proposing. You're compacting strA B; B= func... in the same operation, but this doesn't answer my question. The code I wrote is not meant to be optimal, it was just to illustrate what I am looking for, so avoiding the copy based on temporaries is not the big deal here. – itQ Feb 13 '17 at 12:22
  • @yuri's second proposition is closer to what I was expecting, although not ideal as it computes B even when it is not needed. Many thanks for having given a try! I'll stick with my 1st code. – itQ Feb 13 '17 at 12:22