Как убрать копию при создании цепочки?

10

Я создаю класс типа цепочки, такой как маленький пример ниже. Похоже, что при объединении функций-членов вызывается конструктор копирования. Есть ли способ избавиться от вызова конструктора копирования? В моем примере с игрушкой, приведенным ниже, очевидно, что я имею дело только с временными, и поэтому «должно» (возможно, не по стандартам, но логически) быть правомочным. Второй лучший вариант - скопировать elision - вызывать конструктор перемещения, но это не так.

class test_class {
    private:
    int i = 5;
    public:
    test_class(int i) : i(i) {}
    test_class(const test_class& t) {
        i = t.i;
        std::cout << "Copy constructor"<< std::endl;
    }
    test_class(test_class&& t) {
        i = t.i;
        std::cout << "Move constructor"<< std::endl;
    }
    auto& increment(){
        i++;
        return *this;
    }
};
int main()
{
    //test_class a{7};
    //does not call copy constructor
    auto b = test_class{7};
    //calls copy constructor
    auto b2 = test_class{7}.increment();
    return 0;
}

Изменить: некоторые разъяснения. 1. Это не зависит от уровня оптимизации. 2. В моем реальном коде у меня есть более сложные (например, выделенные в куче) объекты, чем целые

DDaniel
источник
Какой уровень оптимизации вы используете для компиляции?
JVApen
2
auto b = test_class{7};не вызывает конструктор копирования, потому что он действительно эквивалентен, test_class b{7};а компиляторы достаточно умны, чтобы распознать этот случай и поэтому могут легко исключить любое копирование. То же самое не может быть сделано для b2.
Какой-то программист чувак
В показанном примере не может быть никакой разницы между перемещением и копированием, и не все об этом знают. Если вы вставили туда что-то вроде большого вектора, это может быть другое дело. Обычно перемещение имеет смысл только для ресурсов, использующих типы (например, использование большого количества памяти в куче и т. Д.) - так ли это здесь?
darune
Пример выглядит надуманным. У вас действительно есть I / O ( std::cout) в вашем экземпляре ctor? Без этого копия должна быть оптимизирована.
rustyx
@rustyx, удалите std :: cout и сделайте конструктор копирования явным. Это демонстрирует, что разрешение на копирование не зависит от std :: cout.
DDaniel

Ответы:

7
  1. Частичный ответ (он не b2создается на месте, но превращает конструкцию копии в конструкцию перемещения): Вы можете перегрузить incrementфункцию-член в категории значений связанного экземпляра:

    auto& increment() & {
        i++;
        return *this;
    }
    
    auto&& increment() && {
        i++;
       return std::move(*this);
    }

    Это вызывает

    auto b2 = test_class{7}.increment();

    переместить-построить, b2потому что test_class{7}это временно, и &&перегрузка test_class::incrementназывается.

  2. Для истинной конструкции на месте (т.е. даже конструкции перемещения) вы можете превратить все специальные и не специальные функции-члены в constexprверсии. Затем вы можете сделать

    constexpr auto b2 = test_class{7}.increment();

    и вы ни ход, ни копирование конструкции, чтобы заплатить. Это, очевидно, возможно для простого test_class, но не для более общего сценария, который не учитывает constexprфункции-члены.

lubgr
источник
Вторая альтернатива также делает b2немодифицируемым.
Какой-то программист чувак
1
@ Someprogrammerdude Хороший вопрос. Я думаю, это constinitбыл бы путь туда.
19
Каково назначение амперсандов после имени функции? Я никогда не видел этого раньше.
Филипп Нельсон
1
@PhilipNelson они являются ref-квалификаторами и могут определять то thisже самое, что и constфункции-члены
kmdreko
1

По сути, назначение на требует вызова конструктора, то есть копии или перемещения . Это отличается от когда с обеих сторон функции известно, что это один и тот же отдельный объект. Также может ссылаться на общий объект, очень похожий на указатель.


Самый простой способ, вероятно, - сделать конструктор копирования полностью оптимизированным. Настройка значения уже оптимизирована компилятором, это просто то, std::coutчто нельзя оптимизировать.

test_class(const test_class& t) = default;

(или просто удалите и конструктор копирования и перемещения)

живой пример


Поскольку ваша проблема связана со ссылкой, решение, вероятно, не возвращает ссылку на объект, если вы хотите прекратить копирование таким способом.

  void increment();
};

auto b = test_class{7};//does not call copy constructor
b.increment();//does not call copy constructor

Третий метод - в первую очередь просто полагаться на исключение копирования - однако это требует перезаписи или инкапсуляции операции в одну функцию и, таким образом, полностью исключает проблему (я знаю, что это не то, что вы хотите, но это может быть решение для других пользователей):

auto b2 = []{test_class tmp{7}; tmp.increment().increment().increment(); return tmp;}(); //<-- b2 becomes 10 - copy constructor not called

Четвертый метод использует вместо этого перемещение, либо вызывается явным

auto b2 = std::move(test_class{7}.increment());

или как видно в этом ответе .

darune
источник
@ О'Нил думал о другом случае - исправлено
Дарун