Як схилити копію при ланцюжку?


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. У моєму реальному коді у мене складніші (наприклад, виділені купи) об'єкти, ніж ints


Який рівень оптимізації ви використовуєте для компіляції?
JVApen

2
auto b = test_class{7};не викликає конструктор копій, оскільки він дійсно еквівалентний, test_class b{7};і компілятори досить розумні, щоб розпізнати цей випадок, і тому можуть легко ухилитись від будь-якого копіювання. Те ж саме неможливо зробити b2.
Якийсь програміст чувак

У наведеному прикладі може не бути різної різниці між переміщенням та копією, і не всі знають про це. Якщо ви застрягли там щось на зразок великого вектора, це може бути іншою справою. Зазвичай переміщення має сенс лише для використання ресурсів типів (як, наприклад, використання великої кількості пам’яті купи тощо) - це так?
darune

Приклад виглядає надуманим. Чи є у вас насправді I / O ( std::cout) у вашому копіювальному пристрої? Без цього копію слід оптимізувати.
rustyx

@rustyx, видаліть std :: cout і зробіть конструктор копій явним. Це демонструє, що копія elision не залежить від 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функцій членів.


Друга альтернатива також робить b2неможливим зміни.
Якийсь програміст чувак

1
@Someprogrammerdude Добре. Я думаю, це constinitбув би шлях до туди.
лубгр

Яке призначення амперсандів після назви функції? Я ніколи цього не бачив.
Філіп Нельсон

1
@PhilipNelson вони є кваліфікованими і можуть продиктувати те, що 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());

або як видно з цієї відповіді .


@ O'Neil думав про інший випадок - ty, виправлено
darune
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.