Про красивость работы с 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 . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.