Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 15.03.25 12:28
Оценка: :))
Код:

#include <iostream>

class TrackedClass {
private:
    int _id;
    bool _isDesctructed = false;

public:
    // Default constructor
    TrackedClass() {
        std::cout << "Default constructor\n";
    }
    
    TrackedClass(int id) : _id(id) {
        std::cout << "Constructor id=" << id << "\n";
    }
    
    // Destructor
    ~TrackedClass() {
        _isDesctructed = true;
        std::cout << "Destructor id=" << _id << "\n";
    }

    // Copy constructor
    TrackedClass(const TrackedClass& other) {
        std::cout << "Copy constructor\n";
    }

    // Move constructor
    TrackedClass(TrackedClass&& other) noexcept {
        std::cout << "Move constructor\n";
    }

    // Copy assignment operator
    TrackedClass& operator=(const TrackedClass& other) {
        std::cout << "Copy assignment operator\n";
        return *this;
    }

    // Move assignment operator
    TrackedClass& operator=(TrackedClass&& other) noexcept {
        std::cout << "Move assignment operator\n";
        return *this;
    }
    
    void test() {
        std::cout << "desctructed=" << (_isDesctructed ? "true" : "false") << std::endl;
    }
};

class Wrapper {
private:
    TrackedClass _trackedClass;
    
public:
    Wrapper() : _trackedClass(1) {
    }
    
    TrackedClass&& take() {
        return std::move(_trackedClass);;
    }
};

TrackedClass&& fun1() {
    Wrapper w = Wrapper();
    return w.take();
}

int main(int argc, const char * argv[]) {
    TrackedClass&& t = fun1();
    t.test();
    std::cout << "test" << std::endl;
}


Тут можно запустить: https://www.programiz.com/online-compiler/7P7dO9D47lJtm

Мне нужно забрать владение над TrackedClass у Wrapper.

И вот в чем прикол. В онлайн-компиляторе вывод:

Constructor id=1
Destructor id=1
desctructed=false
test


А у меня в XCode тот же самый код:

Constructor id=1
Destructor id=1
desctructed=true
test


Т.е. если так попытаться заиметь владение — можно получить по башке, получается?

Получается в онлайн этом компилере — деструктор то вызывается, но видимо он сбрасывает значение поля? Т.е. в любом случае такое перемещение — не позволяет сохранить объект и передать.

Добавил:

TrackedClass&& fun1() {
    Wrapper w = Wrapper();
    return std::move(w.take());
}


— то же самое.
=сначала спроси у GPT=
Отредактировано 15.03.2025 12:41 Shmj . Предыдущая версия .
Re: Про перемещение (на примере кода)
От: rg45 СССР  
Дата: 15.03.25 12:49
Оценка: +1
Здравствуйте, Shmj, Вы писали:

S>Т.е. если так попытаться заиметь владение — можно получить по башке, получается?


Не можно, а нужно. Время жизни объекта закончилось при выходе из функции. И ссылка, которую ты вернул, оказывается невалидной, прямо сразу же. Всё дальнейшее поведение программы — это UB.

Сколько лет ты уже толчёшься на этом форуме, но не потрудился освоить даже азы. Какие слова тебе нужно сказать, чтоб до тебя дошло. что C++ — не C#?
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 15.03.2025 12:51 rg45 . Предыдущая версия .
Re: Про перемещение (на примере кода)
От: T4r4sB Россия  
Дата: 15.03.25 12:52
Оценка:
Здравствуйте, Shmj, Вы писали:
S> TrackedClass&& take() {

Вот тут убери двойной амперсанд. Ты возвращаешь значение же? Значит так и пиши в сигнатуре

S> TrackedClass&& t = fun1();


Вот тут тоже убери двойной амперсанд. В переменной значение же? Значит так и пиши в объявлении
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re: Про перемещение (на примере кода)
От: rg45 СССР  
Дата: 15.03.25 14:33
Оценка:
Здравствуйте, Shmj, Вы писали:

S>
S>    void test() {
S>        std::cout << "desctructed=" << (_isDesctructed ? "true" : "false") << std::endl;
S>    }
S>


Вот это очень наивная попытка обнаружить окончание времени жизни объекта. Если объект уже разрушен, то вызов нестатической функции-члена для этого объекта порождает неопределённое поведение. Вместо статуса объекта программа может вывести тебе расписание поездов на Луну. А может отформатировать винчестер.
--
Справедливость выше закона. А человечность выше справедливости.
Re: Про перемещение (на примере кода)
От: Muxa  
Дата: 15.03.25 16:16
Оценка: -1 :)
S>Код:

S>

S>class Wrapper {
S>private:
S>    unique_ptr<TrackedClass> _trackedClass;
    
S>public:
S>    Wrapper() : _trackedClass(new TrackedClass(1)) {
S>    }
    
S>    unique_ptr<TrackedClass> take() {
S>        return std::move(_trackedClass);;
S>    }
S>};
Re[2]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 15.03.25 17:34
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>Вот тут тоже убери двойной амперсанд. В переменной значение же? Значит так и пиши в объявлении


Ну смотрите, получается что обычный возврат return из функции — может порождать копию. Так ведь?

class Wrapper {
private:
    TrackedClass _trackedClass;
    
public:
    Wrapper() : _trackedClass(1) {
    }
    
    TrackedClass take() {
        return  _trackedClass; // <- тут копия порождается.
    }
};

TrackedClass fun1() {
    Wrapper w = Wrapper();
    TrackedClass t = w.take(); 
    return t;
}


Однако если срабатывает некая оптимизация, то копия уже не порождается. К примеру тут:

class Wrapper {
private:
    TrackedClass _trackedClass;
    
public:
    Wrapper() : _trackedClass(1) {
    }
    
    TrackedClass take() {
        TrackedClass tc = std::move(_trackedClass); // <- Копия не порождается.
        return tc;
    }
};

TrackedClass fun1() {
    Wrapper w = Wrapper();
    TrackedClass t = w.take(); // <- копия НЕ порождается.
    return t;
}

int main(int argc, const char * argv[]) {
    TrackedClass t = fun1();
    t.test();
    std::cout << "test" << std::endl;
}


Правильно ли я понял, что это благодаря умному компилятору и NRVO ? И всегда ли можно на это рассчитывать, всегда ли имеем 100% гарантию что копия не порождается?
=сначала спроси у GPT=
Отредактировано 15.03.2025 17:52 Shmj . Предыдущая версия .
Re[2]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 15.03.25 17:36
Оценка:
Здравствуйте, rg45, Вы писали:

R>Сколько лет ты уже толчёшься на этом форуме, но не потрудился освоить даже азы. Какие слова тебе нужно сказать, чтоб до тебя дошло. что C++ — не C#?


Так дело вот в чем. Я с 15 декабря примерно без выходных и праздников работаю с другим языком (не прикасался к C++). Мозг работает на 30%.

И тут срочно понадобилось внести правки в старый C++ проект.

И вроде бы разбирался с этим всем — но сейчас другой язык с умным сборщиком мусора очистил и облегчил мой мозг — и приходится как бы заново разбираться.
=сначала спроси у GPT=
Re[2]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 15.03.25 17:46
Оценка:
Здравствуйте, Muxa, Вы писали:

S>> unique_ptr<TrackedClass> _trackedClass;


Вот это, получается, канонический вариант. Но не красивый — добавляет в код угловатости.
=сначала спроси у GPT=
Re[3]: Про перемещение (на примере кода)
От: rg45 СССР  
Дата: 15.03.25 18:34
Оценка: +1
Здравствуйте, Shmj, Вы писали:

S>Так дело вот в чем. Я с 15 декабря примерно без выходных и праздников работаю с другим языком (не прикасался к C++). Мозг работает на 30%.


Смотри какая штука. В С++ никогда нельзя выпускать из виду время жизни объектов. Нет и быть не может универсального ответа на вопрос, как правильно возвращать — по ссылке или по значению. Всё определяется решаемой задачей и семантикой того или иного класса и той или иной функции. Это же относится и к твоему классу враппера. Его можно было бы реализовать и так, и эдак, вероятно, какое-то решение оказалось бы более выгодным. Чтобы выбрать оптимальный вариант дизайна, нужно знать больше деталей, которые не видны в твоём синтетическом примере. Но вот при реализации функции fun1, программист однозначно прошёлкал этот момент. Каким бы ни был дизай класса враппера, как бы он ни возвращал результат, по ссылке или по значению, функция fun1 реализована некорректно и возвращает битую ссылку. И чтобы исправить ошибку, нужно просто сделать, чтоб fun1 возвращала по значению.
--
Справедливость выше закона. А человечность выше справедливости.
Re[3]: Про перемещение (на примере кода)
От: T4r4sB Россия  
Дата: 15.03.25 18:34
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Здравствуйте, T4r4sB, Вы писали:


TB>>Вот тут тоже убери двойной амперсанд. В переменной значение же? Значит так и пиши в объявлении


S>Ну смотрите, получается что обычный возврат return из функции — может порождать копию. Так ведь?


S>Правильно ли я понял, что это благодаря умному компилятору и NRVO ? И всегда ли можно на это рассчитывать, всегда ли имеем 100% гарантию что копия не порождается?


Есть правила когда оптимизация точно сработает.
Есть случаи когда она точно НЕ сработает, и как минимум надо понимать как эта оптимизация устроена технически. Например при возврате поля компилятор не знает нужно ли тебе это поле в дальнейшем, так что если нет то руками ставь std move
И есть случаи когда зависит от компилятора но их сравнительно мало.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[4]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 15.03.25 18:41
Оценка: :)
Здравствуйте, rg45, Вы писали:

R>Каким бы ни был дизай класса враппера, как бы он ни возвращал результат, по ссылке или по значению, функция fun1 реализована некорректно и возвращает битую ссылку. И чтобы исправить ошибку, нужно просто сделать, чтоб fun1 возвращала по значению.


А вот такой вопрос: https://rsdn.org/forum/cpp/8911502.1
Автор: Shmj
Дата: 15.03 20:34


Во втором примере — все работает как нужно, копирования не происходит. Но есть ли гарантия что это будет работать везде?
=сначала спроси у GPT=
Re[3]: Про перемещение (на примере кода)
От: rg45 СССР  
Дата: 15.03.25 18:54
Оценка: 8 (1)
Здравствуйте, Shmj, Вы писали:


S>
S>    TrackedClass take() {
S>        return  _trackedClass; // <- тут копия порождается.
S>    }
S>};
S>


Верно.

S>
S>    TrackedClass take() {
S>        TrackedClass tc = std::move(_trackedClass); // <- Копия не порождается.
S>        return tc;
S>    }
S>


Здесь сначала происходит перемещение содержимого объекта _trackedClass в локальный объект tc. И нужно понимать, что время жизни объекта _trackedClass при этом не заканчивается. Скорлупа этого (под)объекта будет жить, пока живет его полный объект. Также примечательно то, что объект tc является move eligible. Это означает, что, если к нему не будет применена NRVO, то к нему будет применено ещё одно перемещение. Т.е. либо NRVO (что скорее всего), либо второе перемещение, копирования точно не будет.

Как бы то ни было, хотя бы одного перемещения здесь не избежать. Поэтому смысла в этом локальном объекте tc нет никакого. Лучше писать просто:

    TrackedClass take() { return std::move(_trackedClass); }

Так и код проще, и потенциальное число перемещений меньше.

S>
S>TrackedClass fun1() {
S>    Wrapper w = Wrapper();
S>    TrackedClass t = w.take(); // <- копия НЕ порождается.
S>    return t;
S>}
S>


Строго говоря, здесь копия может порождаться, а может нет. Стандарт языка не регламентирует число промежуточных копий, это implementation specifics. Но, если NRVO таки было подключено, что скорее всего, тогда никаких дополнительных копий не будет.

К счастью, этот пример можно легко видоизменить так, чтоб вместо необязательной NRVO применялась обязательная RVO:

TrackedClass fun1() {
    Wrapper w = Wrapper();
    return w.take(); // <- копия НЕ порождается СТОПУДОВО!
}


То, что в этом случает не будет промежуточных копий, гарантируется стандартом языка.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 15.03.2025 19:10 rg45 . Предыдущая версия . Еще …
Отредактировано 15.03.2025 19:08 rg45 . Предыдущая версия .
Отредактировано 15.03.2025 19:06 rg45 . Предыдущая версия .
Отредактировано 15.03.2025 18:57 rg45 . Предыдущая версия .
Re[5]: Про перемещение (на примере кода)
От: rg45 СССР  
Дата: 15.03.25 18:55
Оценка:
Здравствуйте, Shmj, Вы писали:

S>А вот такой вопрос: https://rsdn.org/forum/cpp/8911502.1
Автор: Shmj
Дата: 15.03 20:34

S>Во втором примере — все работает как нужно, копирования не происходит. Но есть ли гарантия что это будет работать везде?

Ответ здесь
Автор: rg45
Дата: 15.03 21:54
.
--
Справедливость выше закона. А человечность выше справедливости.
Re[4]: Про перемещение (на примере кода)
От: rg45 СССР  
Дата: 15.03.25 19:35
Оценка:
Здравствуйте, rg45, Вы писали:

R>Как бы то ни было, хотя бы одного перемещения здесь не избежать. Поэтому смысла в этом локальном объекте tc нет никакого.


А раз уж хотя бы одного перемещения не избежать ни при каких раскладах, твоя функция-член take може спокойненько возвращать rvalue ссылку. И перемешение в этом случае будет происходить не в take, а в fun1:

TrackedClass&& take() {
    return std::move(_trackedClass); // Ни копии, ни перемещения - просто возврат rvalue ссылки.
}

TrackedClass fun1() {
    Wrapper w = Wrapper();
    return w.take(); // То самое одно неизбежное перемещение.
}


Но признаться, меня несколько напрягает такой дизайн. Вот нафига вот это безусловное встраивание move внутрь каждого неконстантного объекта? Не лучше ли просто обеспечить оптимальный доступ к члену класса в зависимости от категории выражения, а вызвающий метод уже пускай сам делает move, если захочет:

class Wrapper {
// . . .    
    const TrackedClass& get() const & { return _trackedClass; }
    TrackedClass&& get() && { return std::move(_trackedClass); }
};

TrackedClass fun1() {
    Wrapper w = Wrapper();
    return std::move(w).get(); // Клиент заказывает перемещение явно. И это хорошо!
}


И опять же, можно проще:

TrackedClass fun1() {
    return Wrapper().get(); // Перемещение автоматом, поскольку объект враппера временный.
}
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 16.03.2025 11:22 rg45 . Предыдущая версия . Еще …
Отредактировано 15.03.2025 20:22 rg45 . Предыдущая версия .
Отредактировано 15.03.2025 19:57 rg45 . Предыдущая версия .
Отредактировано 15.03.2025 19:40 rg45 . Предыдущая версия .
Отредактировано 15.03.2025 19:39 rg45 . Предыдущая версия .
Отредактировано 15.03.2025 19:36 rg45 . Предыдущая версия .
Re[5]: Про перемещение (на примере кода)
От: Marty Пират https://www.youtube.com/channel/UChp5PpQ6T4-93HbNF-8vSYg
Дата: 15.03.25 20:12
Оценка:
Здравствуйте, Shmj, Вы писали:

S>А вот такой вопрос: https://rsdn.org/forum/cpp/8911502.1
Автор: Shmj
Дата: 15.03 20:34


S>Во втором примере — все работает как нужно, копирования не происходит. Но есть ли гарантия что это будет работать везде?


Ну что за чушь? Если UB у тебя случайно работает, как ты ожидаешь, то тебе просто очень повезло. А могло бы и винт отформатировать
Маньяк Робокряк колесит по городу
Re[6]: Про перемещение (на примере кода)
От: Shmj Ниоткуда  
Дата: 16.03.25 08:14
Оценка:
Здравствуйте, Marty, Вы писали:

S>>Во втором примере — все работает как нужно, копирования не происходит. Но есть ли гарантия что это будет работать везде?

M>Ну что за чушь? Если UB у тебя случайно работает, как ты ожидаешь, то тебе просто очень повезло. А могло бы и винт отформатировать

А где вы там увидели UB, тем более уровня критического. Максимум что там может произойти — излишнее копирование.
=сначала спроси у GPT=
Re[5]: Про перемещение (на примере кода)
От: Doom100500 Израиль  
Дата: 16.03.25 10:44
Оценка: :))
Здравствуйте, rg45, Вы писали:

R>[cpp]

R>class Wrapper {
R>// . . .
R> const TrackedClass& get() const & { return _trackedClass; }
R> TrackedClass&& get() && { return std::move(_trackedClass); }
R>};

Опа! А это что за синтаксис? Когда появился? Как называется?
Спасибо за внимание
Re[6]: Про перемещение (на примере кода)
От: rg45 СССР  
Дата: 16.03.25 10:50
Оценка:
Здравствуйте, Doom100500, Вы писали:

D>Здравствуйте, rg45, Вы писали:


R>>[cpp]

R>>class Wrapper {
R>>// . . .
R>> const TrackedClass& get() const & { return _trackedClass; }
R>> TrackedClass&& get() && { return std::move(_trackedClass); }
R>>};

D>Опа! А это что за синтаксис? Когда появился? Как называется?


Называется member function ref-qualifiers. Появился этот синтаксис в C++11. Т.е. 14 лет назад
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 16.03.2025 10:55 rg45 . Предыдущая версия .
Re[6]: Про перемещение (на примере кода)
От: rg45 СССР  
Дата: 16.03.25 11:09
Оценка: +1
Здравствуйте, Doom100500, Вы писали:

D>Здравствуйте, rg45, Вы писали:


R>>
R>>class Wrapper {
R>>// . . .    
R>>    const TrackedClass& get() const & { return _trackedClass; }
R>>    TrackedClass&& get() && { return std::move(_trackedClass); }
R>>};
R>>


D>Опа! А это что за синтаксис? Когда появился? Как называется?


А в C++23 появляется ещё один наворот — explicit object parameter. С помощью него обе эти перегрузки можно будет покрыть одной функцией:

class Wrapper {
// . . .    
    template <typename Self>
    decltype(auto) get(this Self&& self) {
        return (std::forward<Self>(self)._trackedClass); 
    }
};


Если нужно предоставить не все варианты перегрузок, можно добавить констрейнтов по вкусу:

class Wrapper {
// . . .    
    template <typename Self>
    requires (
        std::is_rvalue_refrence_v<Self>
        or std::is_const_v<std::remove_refrerence_t<Self>>)
    decltype(auto) get(this Self&& self) {
        return (std::forward<Self>(self)._trackedClass); 
    }
};


Нравится?
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 16.03.2025 11:14 rg45 . Предыдущая версия . Еще …
Отредактировано 16.03.2025 11:13 rg45 . Предыдущая версия .
Отредактировано 16.03.2025 11:12 rg45 . Предыдущая версия .
Отредактировано 16.03.2025 11:11 rg45 . Предыдущая версия .
Отредактировано 16.03.2025 11:10 rg45 . Предыдущая версия .
Re[7]: Про перемещение (на примере кода)
От: Doom100500 Израиль  
Дата: 16.03.25 11:36
Оценка: :)
Здравствуйте, rg45, Вы писали:

R>Нравится?


Нет слов
Спасибо за внимание
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.