Про красивость работы с std::vector
От: Shmj Ниоткуда  
Дата: 14.03.25 22:41
Оценка: :))) :)
Допустим такая обертка над std::vector:

#include <vector>
#include <cstdint>
#include <cstring>

class MyClass
{
private:
    std::vector<uint8_t> data;

public:
    MyClass() : data(100) {}

    void setHeader(uint32_t value)
    {
        std::memcpy(data.data(), &value, sizeof(value));
    }

    // ... еще устанавливаем разные части бинарного пакета

    const std::vector<uint8_t>& getData() const
    {
        return data;
    }
};


Не очень красиво что при обращении:

std::vector<uint8_t> t = obj.getData();


— будет создавать полную копию всех данных. Как бы постоянная ловушка. Т.е. нужно не забывать писать:

std::vector<uint8_t>& t = obj.getData();


И далее. Что если я захочу в итоге переместить данные, забрать владение у MyClass — как красивее оформить?

std::vector<uint8_t> takeData()
{
    return std::move(data);
}


Вот тут что-то об этом в самом низу: https://pvs-studio.ru/ru/blog/terms/6516/

Здесь вызов std::move следует убрать. Несмотря на то, что в коде пытаются "подсказать" компилятору, что возвращаемый объект следует перемещать, а не копировать, используя функцию std::move, компилятор будет обязан сгенерировать более медленный ассемблерный код.

Дело в том, что возвращаемый объект – это результат вызова std::move, его тип будет Res &&. Тип фактически возвращаемого объекта и тип возвращаемого объекта по сигнатуре функции различны. Следовательно, компилятор не сможет применить для функции foo NRVO, и мы имеем дело не с оптимизацией, а с пессимизацией.

Более того, согласно стандарту C++11, если компилятор не сможет применить необязательную оптимизацию, то он должен сначала применить конструктор перемещения и лишь затем конструктор копирования для локальных переменных или формальных параметров функции.


Честно сказать, не уверен что это к данному случаю. Но как-то все слишком завязано на эти хитрые трюки компилятора по оптимизации, а хотелось бы простоты в данном случае. Даже не полагаться на RVO (вдруг где-то не сработает) это — забрать владение чисто и прозрачно.
=сначала спроси у GPT=
Re: Про красивость работы с std::vector
От: T4r4sB Россия  
Дата: 14.03.25 23:04
Оценка: +1
Здравствуйте, Shmj, Вы писали:

S>Дело в том, что возвращаемый объект – это результат вызова std::move, его тип будет Res &&. Тип фактически возвращаемого объекта и тип возвращаемого объекта по сигнатуре функции различны. Следовательно, компилятор не сможет применить для функции foo NRVO, и мы имеем дело не с оптимизацией, а с пессимизацией.


В данном случае NRVO невозможно, поэтому мув нужен
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[2]: Про красивость работы с std::vector
От: Shmj Ниоткуда  
Дата: 14.03.25 23:29
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>В данном случае NRVO невозможно, поэтому мув нужен


А что там будет — просто RVO? Это гарантируется на всех компиляторах?
=сначала спроси у GPT=
Re: Про красивость работы с std::vector
От: Muxa  
Дата: 15.03.25 06:03
Оценка: -1 :)
S>- будет создавать полную копию всех данных. Как бы постоянная ловушка. Т.е. нужно не забывать писать:

S>
S>std::vector<uint8_t>& t = obj.getData();
S>


Непонятно причем тут вектор?
Оно будет работать аналогично с любым типом возвращаемого объекта.
Хочешь ссылку — дай знать об этом компилятору.

S>И далее. Что если я захочу в итоге переместить данные, забрать владение у MyClass — как красивее оформить?


S>
S>std::vector<uint8_t> takeData()
S>{
S>    return std::move(data);
S>}
S>

S>Здесь вызов std::move следует убрать. Несмотря на то, что в коде пытаются "подсказать" компилятору, что возвращаемый объект следует перемещать, а не копировать, используя функцию std::move, компилятор будет обязан сгенерировать более медленный ассемблерный код.

А если вернуть результат как &&?

Причем тут вектор — до сих пор неясно.
Re[3]: Про красивость работы с std::vector
От: tapatoon  
Дата: 15.03.25 06:08
Оценка: +1
Здравствуйте, Shmj, Вы писали:

S>А что там будет — просто RVO? Это гарантируется на всех компиляторах?


data — это же член класса? Тут RVO не сработает. Функция, которая возвращает локальный объект через RVO на самом деле сразу аллоцирует его в стеке вызывающей функции.
Тут же член класса, никакого RVO не будет.
Центр ИПсО Сил Специальных Операций
Re[2]: Про красивость работы с std::vector
От: Shmj Ниоткуда  
Дата: 15.03.25 07:55
Оценка:
Здравствуйте, Muxa, Вы писали:

M>Непонятно причем тут вектор?


Ну вот тоже QVector — сделан иначе:

QVector<int> vector1 = {1, 2, 3};
QVector<int> vector2 = vector1;

qDebug() << (vector1.data() == vector2.data());  // true, данные общие


— не создается копия данных на каждый чих, что более разумно на мой взгляд.

M>Оно будет работать аналогично с любым типом возвращаемого объекта.

M>Хочешь ссылку — дай знать об этом компилятору.

M>А если вернуть результат как &&?


Ну, наверное, так.
=сначала спроси у GPT=
Re[4]: Про красивость работы с std::vector
От: Shmj Ниоткуда  
Дата: 15.03.25 08:09
Оценка:
Здравствуйте, tapatoon, Вы писали:

T>data — это же член класса? Тут RVO не сработает. Функция, которая возвращает локальный объект через RVO на самом деле сразу аллоцирует его в стеке вызывающей функции.

T>Тут же член класса, никакого RVO не будет.

std::vector<uint8_t>&& takeData()
{
    std::vector<uint8_t>&& temp = std::move(data); // temp - rvalue-ссылка на data
    return temp;
}


А вот так если записать?
=сначала спроси у GPT=
Re[3]: Про красивость работы с std::vector
От: Vzhyk2  
Дата: 15.03.25 08:15
Оценка:
Здравствуйте, Shmj, Вы писали:

S>- не создается копия данных на каждый чих, что более разумно на мой взгляд.

Нет.
Просто есть два подхода:
1. В одном при создании копии объекта данные не копируются и вводится явная функция типа clone. В OpenCV, например, такой подход.
2. В другом при создании копии данные копируются, но ты можешь пользоваться ссылками. В STL такой подход.

Определяется это типичными задачами в которых твой объект юзаются.
Re[3]: Про красивость работы с std::vector
От: T4r4sB Россия  
Дата: 15.03.25 08:24
Оценка:
Здравствуйте, Shmj, Вы писали:

S>А что там будет — просто RVO? Это гарантируется на всех компиляторах?


Никакого VO не будет. Невозможно это сделать для отдельного поля. Изучи, как RVO работает, чтоб было понятно, почему это так
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[5]: Про красивость работы с std::vector
От: T4r4sB Россия  
Дата: 15.03.25 08:25
Оценка:
Здравствуйте, Shmj, Вы писали:

S>
S>std::vector<uint8_t>&& takeData()
S>{
S>    std::vector<uint8_t>&& temp = std::move(data); // temp - rvalue-ссылка на data
S>    return temp;
S>}
S>


S>А вот так если записать?


Тогда будет RVO
Но этот код эквивалентен

return std::move(data)
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[3]: Про красивость работы с std::vector
От: T4r4sB Россия  
Дата: 15.03.25 08:27
Оценка:
Здравствуйте, Shmj, Вы писали:

S>- не создается копия данных на каждый чих, что более разумно на мой взгляд.


Copy-on-write что ли? Да даже тут где-то на форуме разбирали, почему это плохо
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[6]: Про красивость работы с std::vector
От: Shmj Ниоткуда  
Дата: 15.03.25 09:02
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>Тогда будет RVO


Гарантируется ли он на всех компиляторах под все платформы или же не факт? Можно ли на это опираться?
=сначала спроси у GPT=
Re[7]: Про красивость работы с std::vector
От: andrey.desman  
Дата: 15.03.25 09:06
Оценка: -1 :)
Здравствуйте, Shmj, Вы писали:

TB>>Тогда будет RVO

S>Гарантируется ли он на всех компиляторах под все платформы или же не факт? Можно ли на это опираться?

Да нет никакого RVO, просто вернется ссылка. Вот если бы был тип возвращаемого значения вектор без ссылки, тогда да.
Re[8]: Про красивость работы с std::vector
От: andrey.desman  
Дата: 15.03.25 09:10
Оценка: -1 :)
Здравствуйте, andrey.desman, Вы писали:

TB>>>Тогда будет RVO

S>>Гарантируется ли он на всех компиляторах под все платформы или же не факт? Можно ли на это опираться?
AD>Да нет никакого RVO, просто вернется ссылка. Вот если бы был тип возвращаемого значения вектор без ссылки, тогда да.

Да и то, тут возможна копия, потому как промежуточная ссылка, но не уверен — тут недавно было обсуждение на эту тему.

Просто пиши
vector get() { return std::move(memberVector);}
Re[6]: Про красивость работы с std::vector
От: rg45 СССР  
Дата: 15.03.25 09:10
Оценка:
Здравствуйте, T4r4sB, Вы писали:

S>>
S>>std::vector<uint8_t>&& takeData()
S>>{
S>>    std::vector<uint8_t>&& temp = std::move(data); // temp - rvalue-ссылка на data
S>>    return temp;
S>>}
S>>


S>>А вот так если записать?


TB>Тогда будет RVO

TB>Но этот код эквивалентен

TB>
TB>return std::move(data)
TB>


Не будет здесь никакого RVO. Во втором варианте будет просто возврат rvalue ссылки и только. Причём, будет ли выполнено перемщение данных вектора, не известно — это зависит от того, каким образом вызывающая сторона использует полученную ссылку. Вариант же, предложенный Shmj и вовсе не скомпилируется, поскольку выражение temp относится к категории lvalue и не может быть связано с rvalue сылкой.

А чтобы был RVO (aka guaranteed copy elision), нужно, чтоб выражение под return относилось к категории prvalue и тип этого выражения должен совпадать с типом возращаемого значения функции. Выражение же std::move(data) принадлежит к категори xvalue.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 15.03.2025 10:01 rg45 . Предыдущая версия . Еще …
Отредактировано 15.03.2025 9:27 rg45 . Предыдущая версия .
Отредактировано 15.03.2025 9:16 rg45 . Предыдущая версия .
Re[3]: Про красивость работы с std::vector
От: rg45 СССР  
Дата: 15.03.25 09:23
Оценка:
Здравствуйте, Shmj, Вы писали:

S>
S>QVector<int> vector1 = {1, 2, 3};
S>QVector<int> vector2 = vector1;

S>qDebug() << (vector1.data() == vector2.data());  // true, данные общие
S>


S>- не создается копия данных на каждый чих, что более разумно на мой взгляд.


У меня такое ощущение, что ты пытаешься писать на C++ как на C#. По-моему, давно пора бы расстаться с иллюзиями и понять, что это разные языки. Если ты не хочешь, чтоб создавалась копия, значит, нужно объявлять ссылку. Мало того, нужно ещё и подобрать правильный тип для ссылки — lvalue или rvalue, константная или неконстантная.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 15.03.2025 9:28 rg45 . Предыдущая версия . Еще …
Отредактировано 15.03.2025 9:24 rg45 . Предыдущая версия .
Re: Про красивость работы с std::vector
От: Zhendos  
Дата: 15.03.25 11:23
Оценка:
Здравствуйте, Shmj, Вы писали:

S>Не очень красиво что при обращении:


S>
S>std::vector<uint8_t> t = obj.getData();
S>


S>- будет создавать полную копию всех данных. Как бы постоянная ловушка. Т.е. нужно не забывать писать:



Можно в этом случае возвращать std::span
Re[7]: Про красивость работы с std::vector
От: T4r4sB Россия  
Дата: 15.03.25 11:41
Оценка:
Здравствуйте, rg45, Вы писали:

R>Не будет здесь никакого RVO. Во втором варианте будет просто возврат rvalue ссылки и только. Причём, будет ли выполнено перемщение данных вектора, не известно — это зависит от того, каким образом вызывающая сторона использует полученную ссылку. Вариант же, предложенный Shmj и вовсе не скомпилируется, поскольку выражение temp относится к категории lvalue и не может быть связано с rvalue сылкой.


Я имею в виду такой вариант: https://godbolt.org/z/5q55ofhaj
а вот ставить && при объявлении переменных это шиза конечно
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re: Про красивость работы с std::vector
От: tapatoon  
Дата: 15.03.25 11:55
Оценка:
Здравствуйте, Shmj, Вы писали:

S>
S>std::vector<uint8_t> t = obj.getData();
S>

S>- будет создавать полную копию всех данных. Как бы постоянная ловушка. Т.е. нужно не забывать писать:
S>
S>std::vector<uint8_t>& t = obj.getData();
S>


Можно запретить копирование
MyClass(const MyClass&) = delete;
MyClass& operator=(const MyClass&) = delete;
Центр ИПсО Сил Специальных Операций
Re[8]: Про красивость работы с std::vector
От: rg45 СССР  
Дата: 15.03.25 12:34
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>Я имею в виду такой вариант: https://godbolt.org/z/5q55ofhaj

TB>а вот ставить && при объявлении переменных это шиза конечно

Ну так и в этом случае не RVO, а перемещение. Что и видно по выхлопу.

Вот RVO:

https://godbolt.org/z/ee51sTeez

#include <vector>
#include <cstdint>
#include <cstring>
#include <cstdio>

struct T {
    int value{};

    explicit T(int value) : value(value) { printf("T(%d)\n", value);}
    ~T() { printf("~T(%d)\n", value);}

    T(const T&) = delete;
    T(T&&) = delete;
    T& operator=(const T&) = delete;
    T& operator=(T&&) = delete;
};

T takeData(int value)
{
    return T(value);
}

int main() {
    auto t = takeData(42);
}


С практической точки зрения гарантированное RVO, которое ввели, начиная с C++17, позволяет определять кастомные конструкторы за пределами класса. С определёнными ограничениями, конечно же.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 15.03.2025 12:41 rg45 . Предыдущая версия . Еще …
Отредактировано 15.03.2025 12:39 rg45 . Предыдущая версия .
Re[9]: Про красивость работы с std::vector
От: T4r4sB Россия  
Дата: 15.03.25 12:45
Оценка:
Здравствуйте, rg45, Вы писали:


R>Ну так и в этом случае не RVO, а перемещение. Что и видно по выхлопу.


Из поля в локалку — перемещение. А вот return локалки уже сработал через rvo.
Но он мог бы просто написать return std::move для поля
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[2]: Про красивость работы с std::vector
От: Shmj Ниоткуда  
Дата: 15.03.25 12:46
Оценка:
Здравствуйте, Muxa, Вы писали:

M>А если вернуть результат как &&?


А тут смотрите какая ловушка то: https://rsdn.org/forum/cpp/8911389.flat
Автор: Shmj
Дата: 15.03 15:28


Получается, после уничтожения Wrapper — оъект уничтожается, хотя владение вроде забрали.
=сначала спроси у GPT=
Re[10]: Про красивость работы с std::vector
От: rg45 СССР  
Дата: 15.03.25 13:07
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>Из поля в локалку — перемещение. А вот return локалки уже сработал через rvo.


Никогда RVO не работал для "локалок". Для "локалок" МОЖЕТ сработать NRVO. А может не сработать. Это уже как компилятор решит. В отличие от RVO, NRVO не гарантируется, это всего лишь необязательная оптимизация. Но только это не относится к твоему случаю, так как у тебя "локалка" — это сслыка. И перемещение у тебя произошло как раз при выходе из функции, а вовсе не при взятии rvalue ссылки на поле. Само по себе взятие ссылки не влечё никакого перемещения. Перемещение происходит при ИСПОЛЬЗОВАНИИ rvalue ссылки. В данном случае при выполнении оператора return.

А для того, чтоб было так, как ты описал, тебе переменную temp нужно обявить как объектный тип, а не как ссылку: https://godbolt.org/z/4j7fjGcTe. Лишь с маленькой поправкой: не RVO, а NRVO. Правда, выхлоп программы от этого изменения никак не изменится Программа у тебя написана так, что позволяет определить, что перемещение произошло один раз. Но когда это произошло, этого не видно. А чтоб увидеть момент перемещения, можно слегка расширить отладочную печать: https://godbolt.org/z/d7fY5e8nh
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 15.03.2025 13:13 rg45 . Предыдущая версия .
Re[11]: Про красивость работы с std::vector
От: T4r4sB Россия  
Дата: 15.03.25 13:11
Оценка: :)
Здравствуйте, rg45, Вы писали:

R>Но только это не относится к твоему случаю, так как у тебя "локалка" — это сслыка. И перемещение у тебя произошло как раз при выходе из функции, а вовсе не при взятии rvalue ссылки на поле.


Да, походу так и есть: https://godbolt.org/z/39MjzzPq5

Короче, нефиг объявлять переменные и возвращаемые типы с &&, у меня от этого крыша едет
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[12]: Про красивость работы с std::vector
От: rg45 СССР  
Дата: 15.03.25 13:22
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>Да, походу так и есть: https://godbolt.org/z/39MjzzPq5


Прикольно, независимо друг от друга мы с тобой написали идентичный код: https://godbolt.org/z/d7fY5e8nh
--
Справедливость выше закона. А человечность выше справедливости.
Re[12]: Про красивость работы с std::vector
От: qaz77  
Дата: 15.03.25 13:23
Оценка:
Здравствуйте, T4r4sB, Вы писали:
TB>Короче, нефиг объявлять переменные и возвращаемые типы с &&, у меня от этого крыша едет

Вот я, когда на С++ 11 переходил, тоже удивлялся насчет && в возвращаемом типе.
Непонятно, почему это вообще это не запрещено на уровне стандарта/компиляторов.

Есть мысли, когда локальные переменные и возвращаемые значения с && могут быть полезны?
Re: Про красивость работы с std::vector
От: m2user  
Дата: 15.03.25 14:27
Оценка:
S>Не очень красиво что при обращении:

S>
S>std::vector<uint8_t> t = obj.getData();
S>


S>- будет создавать полную копию всех данных. Как бы постоянная ловушка. Т.е. нужно не забывать писать:


S>
S>std::vector<uint8_t>& t = obj.getData();
S>


Можно обернуть vector в shared_ptr (и поле класса и тип возвращаемого значения getData().
Тоже не очень красиво выглядит, но зато думать насчет move и пр. NRVO уже не понадобится.
Либо std::span (но он только в C++20, хотя написать свой аналог для частного случая IMHO несложно).
Отредактировано 15.03.2025 18:48 m2user . Предыдущая версия .
Re: Про красивость работы с std::vector
От: sergii.p  
Дата: 16.03.25 09:09
Оценка: 1 (1)
Здравствуйте, Shmj, Вы писали:

то, что vector (и не только он) скрытно может копировать данные — это действительно проблема всей stl. Я бы решал её переопределением своего "пуританского" вектора (без блекджека).

template <typename T>
class greedy_vector : public std::vector<T> {
public:
    using std::vector<T>::vector;

    greedy_vector explicit_copy() const {
        return *this;
    }

private:
    greedy_vector(const greedy_vector& other): std::vector<T>(other) {}
};


теперь просто так не скопируешь:

greedy_vector<int> data{100};
const auto copy = data; // ошибка
Re[2]: Про красивость работы с std::vector
От: rg45 СССР  
Дата: 16.03.25 10:25
Оценка:
Здравствуйте, sergii.p, Вы писали:

SP>то, что vector (и не только он) скрытно может копировать данные — это действительно проблема всей stl. Я бы решал её переопределением своего "пуританского" вектора (без блекджека).


SP>
SP>template <typename T>
SP>class greedy_vector : public std::vector<T> {
SP>public:
SP>    using std::vector<T>::vector;

SP>    greedy_vector explicit_copy() const {
SP>        return *this;
SP>    }

SP>private:
SP>    greedy_vector(const greedy_vector& other): std::vector<T>(other) {}
SP>};
SP>


SP>теперь просто так не скопируешь:


SP>
SP>greedy_vector<int> data{100};
SP>const auto copy = data; // ошибка
SP>


Я бы это задизайнил так:

http://coliru.stacked-crooked.com/a/68033678ff7f1a67

#include <vector>

template <typename T>
class greedy_vector : public std::vector<T> {
public:
    using std::vector<T>::vector;

    friend greedy_vector explicit_copy(const greedy_vector& orig) {
        return greedy_vector(orig); // guaranteed RVO
    }

private:
    greedy_vector(const greedy_vector&) = default;
};

int main()
{
    greedy_vector<int> data{100};
    const auto copy = explicit_copy(data);
}


Благодаря guaranteed copy elision, такая реализация explicit_copy равнозначна конструктору, с практической точки зрения. И для конструктора копирования здесь вполне подойдет вариант, сгенерированный компилятором.
--
Справедливость выше закона. А человечность выше справедливости.
Отредактировано 16.03.2025 10:44 rg45 . Предыдущая версия . Еще …
Отредактировано 16.03.2025 10:43 rg45 . Предыдущая версия .
Отредактировано 16.03.2025 10:40 rg45 . Предыдущая версия .
Отредактировано 16.03.2025 10:39 rg45 . Предыдущая версия .
Отредактировано 16.03.2025 10:29 rg45 . Предыдущая версия .
Отредактировано 16.03.2025 10:28 rg45 . Предыдущая версия .
Re: Про красивость работы с std::vector
От: B0FEE664  
Дата: 17.03.25 15:10
Оценка: -1 :)
Здравствуйте, Shmj, Вы писали:

  Скрытый текст
S>Допустим такая обертка над std::vector:

S>
S>#include <vector>
S>#include <cstdint>
S>#include <cstring>

S>class MyClass
S>{
S>private:
S>    std::vector<uint8_t> data;

S>public:
S>    MyClass() : data(100) {}

S>    void setHeader(uint32_t value)
S>    {
S>        std::memcpy(data.data(), &value, sizeof(value));
S>    }

S>    // ... еще устанавливаем разные части бинарного пакета

S>    const std::vector<uint8_t>& getData() const
S>    {
S>        return data;
S>    }
S>};
S>


S>Не очень красиво что при обращении:

std::vector<uint8_t> t = obj.getData();

S>- будет создавать полную копию всех данных. Как бы постоянная ловушка.
Никакая это не ловушка. Это общепринятая практика.

S>Т.е. нужно не забывать писать:

std::vector<uint8_t>& t = obj.getData();

const пропущен.

S>И далее. Что если я захочу в итоге переместить данные, забрать владение у MyClass — как красивее оформить?

Написать метод swap

S>
S>std::vector<uint8_t> takeData()
S>{
S>    return std::move(data);
S>}
S>

Так можно, но зачем?

Человек, что боится забыть поставить '&' должен так же бояться забыть написать obj.takeData(); без присвоения
for(uint8_t n : obj.takeData())
  std::cout << ' ' << n;
std::cout << '\n';

// ой, obj пустой :(
И каждый день — без права на ошибку...
Отредактировано 17.03.2025 15:49 B0FEE664 . Предыдущая версия .
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.