Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++11 optimal parameter passing

Consider these classes:

#include <iostream>
#include <string>

class A
{
    std::string test;
public:
    A (std::string t) : test(std::move(t)) {}
    A (const A & other) { *this = other; }
    A (A && other) { *this = std::move(other); }

    A & operator = (const A & other)
    {
        std::cerr<<"copying A"<<std::endl;
        test = other.test;
        return *this;
    }

    A & operator = (A && other)
    {
        std::cerr<<"move A"<<std::endl;
        test = other.test;
        return *this;
    }
};

class B
{
    A a;
public:   
    B (A && a) : a(std::move(a)) {}
    B (A const & a) : a(a) {}
};

When creating a B, I always have an optimal forward path for A, one move for rvalues or one copy for lvalues.

Is it possible to achieve the same result with one constructor? It's not a big problem in this case, but what about multiple parameters? I would need combinations of every possible occurrence of lvalues and rvalues in the parameter list.

This is not limited to constructors, but also applies to function parameters (e.g. setters).

Note: This question is strictly about class B; class A exists only to visualize how the copy/move calls gets executed.

like image 450
fscan Avatar asked May 06 '12 16:05

fscan


People also ask

What is parameter passing in C?

Parameter passing involves passing input parameters into a module (a function in C and a function and procedure in Pascal) and receiving output parameters back from the module. For example a quadratic equation module requires three parameters to be passed to it, these would be a, b and c.

Which parameter passing methods are allowed in C Plus Plus?

Parameter Passing in C++ There are three parameter-passing modes in C++: by value, by pointer, and by reference.

What are different parameter passing methods?

Formal Parameter : A variable and its type as they appear in the prototype of the function or method. Actual Parameter : The variable or expression corresponding to a formal parameter that appears in the function or method call in the calling environment. Modes: IN: Passes info from caller to callee.

Which is better pass by value or pass by reference?

Pass-by-references is more efficient than pass-by-value, because it does not copy the arguments. The formal parameter is an alias for the argument. When the called function read or write the formal parameter, it is actually read or write the argument itself.


2 Answers

The "by-value" approach is an option. It is not as optimal as what you have, but only requires one overload:

class B
{
    A a;
public:   
    B (A _a) : a(move(_a)) {}
};

The cost is 1 extra move construction for both lvalues and xvalues, but this is still optimal for prvalues (1 move). An "xvalue" is an lvalue that has been cast to rvalue using std::move.

You could also try a "perfect forwarding" solution:

class B
{
    A a;
public:   
    template <class T,
              class = typename std::enable_if
              <
                 std::is_constructible<A, T>::value
              >::type>
    B (T&& _a) : a(std::forward<T>(_a)) {}
};

This will get you back to the optimal number of copy/move constructions. But you should constrain the template constructor such that it is not overly generic. You might prefer to use is_convertible instead of is_constructible as I've done above. This is also a single constructor solution, but as you add parameters, your constraint gets increasingly complicated.

Note: The reason the constraint is necessary above is because without, clients of B will get the wrong answer when they query std::is_constructible<B, their_type>::value. It will mistakenly answer true without a proper constraint on B.

I would say that none of these solutions is always better than the others. There are engineering tradeoffs to be made here.

like image 89
Howard Hinnant Avatar answered Sep 19 '22 07:09

Howard Hinnant


Use a deduced parameter type for the constructor for B:

template <typename T> explicit B(T && x) : a(std::forward<T>(x) { }

This will work for any argument from which an A object is constructible.

If A has multiple constructors with a varying number of arguments, you can just make the whole thing variadic by adding ... everywhere.

As @Howard says, though, you should add a constraint so that the class doesn't appear to be constructible from arguments from which it really isn't.

like image 23
Kerrek SB Avatar answered Sep 19 '22 07:09

Kerrek SB