c ++ 11 оптимальная передача параметров

Рассмотрим эти классы:

<code>#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) {}
};
</code>

При созданииBУ меня всегда есть оптимальный путь впередA, один ход для значений или один экземпляр для значений.

Можно ли добиться того же результата с одним конструктором? В этом случае это не большая проблема, но как насчет множества параметров? Мне понадобятся комбинации всех возможных вхождений lvalue и rvalues в список параметров.

Это не ограничивается конструкторами, но также относится к параметрам функции (например, к установщикам).

Примечание: этот вопрос строго оclass B; class A существует только для визуализации того, как выполняются вызовы копирования / перемещения.

 fscan06 мая 2012 г., 19:18
@JamesCuster: я просто хотел проверить, сколько раз вызывались соответствующие конструкторы / операторы.
 James Custer06 мая 2012 г., 18:41
Вы должны прочитать это:stackoverflow.com/questions/8472208/… а такжеstackoverflow.com/questions/4782757/…

Ответы на вопрос(3)

Используйте выведенный тип параметра для конструктора дляB:

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

Это будет работать для любого аргумента, от которогоA объект конструктивен.

ЕслиA @ есть несколько конструкторов с различным количеством аргументов, вы можете просто сделать все это вариативным, добавив... где угодно

Как @Ховард говорит, что вы должны добавить ограничение, чтобы класс не казался конструктивным из аргументов, из которых он на самом деле не является.

Решение Вопроса

по стоимости" является опцией. Это не так оптимально, как у вас, но требует только одной перегрузки:

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

Стоимость составляет 1 дополнительное построение хода как для lvalues, так и для xvalue, но это все же оптимально для prvalues (1 ход). «Xvalue» - это lvalue, которое было приведено к rvalue с использованием std :: move.

Вы также можете попробовать "идеальное решение для пересылки":

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)) {}
};

Это вернет вас к оптимальному количеству конструкций копирования / перемещения. Но вы должны ограничить конструктор шаблона так, чтобы он не был слишком универсальным. Вы можете предпочесть использовать is_convertible вместо is_constructible, как я делал выше. Это также решение для одного конструктора, но когда вы добавляете параметры, ваше ограничение становится все более сложным.

Заметк: Причина, по которой ограничение необходимо выше, заключается в том, что без, клиентыB получит неправильный ответ, когда они запросятstd::is_constructible<B, their_type>::value. Он будет ошибочно отвечать истина без надлежащего ограничения наB.

Я бы сказал, что ни одно из этих решений не всегда лучше, чем другие. Здесь есть технические компромиссы.

 Howard Hinnant06 мая 2012 г., 18:54
Интересный. Спасибо за эту информацию. У меня нет доступа к vs2010.
 fscan06 мая 2012 г., 18:53
с учетом значения: в моем тесте у меня был дополнительный ход и для реальных значений (vs2010).
 Howard Hinnant06 мая 2012 г., 19:20
Три основных решения были изложены. У каждого из них есть свои преимущества и недостатки. В настоящее время я не знаю каких-либо других решений, которые конкурируют с этими тремя.
 fscan06 мая 2012 г., 19:05
Хорошо, это хорошо, но делает код нечитаемым, если применяется к каждому конструктору / установщику. Есть ли случай, я бы не хотел этого поведения от конструктора / сеттера. Если нет, не может ли компилятор сделать это по умолчанию, если я объявлю по значению? (также автоматически конвертировать в xrvalue, если lvalue больше никогда не используется, как в этом конструкторе)
 Jonathan Wakely06 мая 2012 г., 19:21
Компилятор может сделать правильную вещь по умолчанию, еслиB был совокупным значением (среди прочего)B::a былpublic а такжеB @ не было объявленных пользователем конструкторов. Тогда вы могли бы просто построитьB какB b{ A() }; который бы сдвинул конструкциюB::a из временного, или вы могли бы сделатьB b{ a2 }; который скопировал бы конструкциюB::a из lvaluea2. Но как только у вас появятся конструкторы, объявленные пользователем, вам нужно сказать, каковы их типы параметров, и четко указать, что вы делаете с этими параметрами.

string в вашем образцеstd::string, просто пофиг: по умолчанию предоставлено копирование и перемещение вызывает их соответствующих в членах. А такжеstd::string имеет функции копирования и перемещения, так что временные перемещения перемещаются, переменные копируютс

Нет необходимости определять конкретную копию и перемещать ctor и назначать. Вы можете просто уйти с конструктором

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

В общем, простая реализация копирования и перемещения может быть следующе

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;
};

Theoperator= принимает значение, которое перемещается внутри. это происходит из временного перемещается, если это происходит из переменной, копируется. Разница между копированием и перемещением требует различных конструкторов, но, если мы получим A как

class B: public A
{
...
};

Нет необходимости переопределять что-либо, поскольку копирующий ctor по умолчанию для B вызывает копию для A, а ход по умолчанию для B вызывает перемещение для A, а все операторы присваивания по умолчанию для B вызывают только один, определенный для A (который перемещается или копируется в зависимости от того, что было переслано).

Ваш ответ на вопрос