c++11 optimal parameter passing

折月煮酒 提交于 2019-12-18 04:14:53

问题


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.


回答1:


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.




回答2:


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.




回答3:


If the string in your sample is std::string, simply don't care: the default provided copy and move calls their respective in members. And std::string has copy and move both implemented, so that temporaries are moved, variables are copied.

There is no need to define specific copy and move ctor and assign. You can just leave with the constructor

A::A(string s) :test(std::move(s)) {}

In general a straightforward implementation of copy and move can be the following

class A
{
public:
    A() :p() {}

    A(const A& a) :p(new data(*a.p)) {} //copy
    A(A&& a) :p(a.p) { a.p=0; }         //move

    A& operator=(A a) //note: pass by value
    { clear(); swap(a); return *this; }
    ~A() { clear(); }

    void swap(A& a) { std::swap(p,a.p); }
    void clear() { delete p; p=0; }

private:

    data* p;
};

The operator= takes a value that is internally moved. If it comes from a temporary is moved, if it comes from a variable is copied. The difference between copy and move requires distinct constructors but, if we derive A as

class B: public A
{
...
};

there is no need to override anything, since the default copy-ctor for B calls the copy for A, and the default move for B calls the move for A, and all the default assign operators for B call the only one defined for A (that moves or copy depending what has been forwarded).



来源:https://stackoverflow.com/questions/10472179/c11-optimal-parameter-passing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!