Russian Qt Forum

Программирование => С/C++ => Тема начата: Kurles от Июль 11, 2016, 17:21



Название: Протаскивание большой структуры через череду return
Отправлено: Kurles от Июль 11, 2016, 17:21
Доброго времени суток.

Иногда нужно протащить тяжеловесную структуру через несколько return (тот же паттерн pimpl у Qt).

Синтетический пример:

Код
C++ (Qt)
#include <stdio.h>
 
struct SomeLargeStuct {
   char data[100000];
   // ....
};
 
SomeLargeStuct f1 () {
   SomeLargeStuct s;
   s.data[0] = 'a';
   s.data[1] = 0x0a;
   s.data[2] = 0x00;
   return s;
}
 
SomeLargeStuct f2() {
   return f1();
}
 
SomeLargeStuct f3() {
   return f2();
}
 
 
int main(int argc, char *argv[])
{
   (void)argc;
   (void)argv;
   auto r = f3();
   printf(r.data);
   return 0;
}
 

Задумался над накладными расходами, он же по идее должен каждый раз в новой функции через конструктор копирования новый объект создавать? Добавил для теста в SomeLargeStuct отладочный вывод как в конструктор копирования так и в обычный конструктор - судя по выводу обычный конструктор вызвался один раз, конструктор копирования же совсем не вызывался.

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



Название: Re: Протаскивание большой структуры через череду return
Отправлено: kai666_73 от Июль 11, 2016, 17:32
Почитайте о семантике перемещения в новом стандарте крестов, реализуйте ее для своей структуры  ;)


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Racheengel от Июль 11, 2016, 17:41
Вместо того, чтобы сделать по-нормальному и передавать в функцию неконстантную ссылку на возвращаемое значение (как это умеет Паскаль с 70-х годов), язык дополнили очередным костылем :( 21 век, мля...


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Old от Июль 11, 2016, 20:57
Вместо того, чтобы сделать по-нормальному и передавать в функцию неконстантную ссылку на возвращаемое значение (как это умеет Паскаль с 70-х годов), язык дополнили очередным костылем :( 21 век, мля...
По нормальному?  ::)
А что в этом нормального? Это же костыль.


Название: Re: Протаскивание большой структуры через череду return
Отправлено: _Bers от Июль 11, 2016, 20:59
Задумался над накладными расходами, он же по идее должен каждый раз в новой функции через конструктор копирования новый объект создавать? Добавил для теста в SomeLargeStuct отладочный вывод как в конструктор копирования так и в обычный конструктор - судя по выводу обычный конструктор вызвался один раз, конструктор копирования же совсем не вызывался.

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

стандарт обязывает компиляторы поддерживать rvo/nrvo оптимизации.
более того, стандарт разрешает компиляторам
забить на любые возможные эффекты
в конструкторе копии,
что бы у последних было больше шансов на оптимизацию.
cм. cтандарт: 12.8/31 "copy elision"

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

на практике, компилятор может не суметь оптимизировать,
если ему не доступен контекст использования.
например, в хедере объявлена функция, которая возвращает по значению,
но её реализация компилятору не доступна
(допустим, реализация находится уже в собранной либке)

лично я не стал бы закладываться на оптимизацию,
и передавал бы по ссылке:

Код:
#include <stdio.h>
 
struct SomeLargeStuct {
    char data[100000];
    // ....
};
 
void f1 (SomeLargeStuct& s) {
    s.data[0] = 'a';
    s.data[1] = 0x0a;
    s.data[2] = 0x00;
}
 
void f2(SomeLargeStuct& obj) {
    f1(obj);
}
 

// --- реализация находится в хедере
// такую функцию даже древние компиляторы
// смогут оптимизировать
inline SomeLargeStuct f3() {
    SomeLargeStuct obj;
    f2(obj);
    return obj;
}
 
 
int main(int argc, char *argv[])
{
    (void)argc;
    (void)argv;
    auto r = f3();
    printf(r.data);
    return 0;
}



Название: Re: Протаскивание большой структуры через череду return
Отправлено: Racheengel от Июль 11, 2016, 21:39
А что в этом нормального? Это же костыль.

Костыль - это как раз Move semantics, вынуждающая вводить "еще один" тип конструктора.
Вместо этого достаточно было бы зарезервированного слова, по примеру this.
Что-то типа, например, result.
Таким образом, пример выше мог бы выглядеть как

SomeLargeStuct f1 () {
    // SomeLargeStuct s; // это уже не надо.
    result.data[0] = 'a';
    result.data[1] = 0x0a;
    result.data[2] = 0x00;
    //return s; // и это тоже в лес
}

а вызов выглядел бы в классическом виде:

SomeLargeStuct s1 = f1();

Таким образом, в контекст f1 под алиасом result попадает ссылка на объект s1, который может быть непосредственно модифицирован.



Название: Re: Протаскивание большой структуры через череду return
Отправлено: Kurles от Июль 11, 2016, 21:55
стандарт обязывает компиляторы поддерживать rvo/nrvo оптимизации.
Спасибо, погуглил по выделенным ключевым словам - в моём примере оно и работает по-ходу.


Название: Re: Протаскивание большой структуры через череду return
Отправлено: kai666_73 от Июль 11, 2016, 23:31
А что в этом нормального? Это же костыль.

Костыль - это как раз Move semantics, вынуждающая вводить "еще один" тип конструктора.
Вместо этого достаточно было бы зарезервированного слова, по примеру this.
Что-то типа, например, result.
Таким образом, пример выше мог бы выглядеть как

SomeLargeStuct f1 () {
    // SomeLargeStuct s; // это уже не надо.
    result.data[0] = 'a';
    result.data[1] = 0x0a;
    result.data[2] = 0x00;
    //return s; // и это тоже в лес
}

а вызов выглядел бы в классическом виде:

SomeLargeStuct s1 = f1();

Таким образом, в контекст f1 под алиасом result попадает ссылка на объект s1, который может быть непосредственно модифицирован.


Не ну конешно это было бы здорово... только вот c/cpp и паскаль сильно разные вещи. Так, например, в паскале вызов ф-ии должен быть к чему-то приэссайнен, иначе код не скомпилируется. А вот в плюсах сие совершенно необязательно, и куда, в случае подвисшего вызова функции, девать возвращенный объект с его ссылкой и кто будет отвечать за его удаление?


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Racheengel от Июль 12, 2016, 01:54
дык, если функция подвисла, то она и так до возврата не дойдет :)
а вообще - нет объекта - нет возврата.


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Igors от Июль 12, 2016, 08:47
Собственно вопрос - я могу на данное поведение рассчитывать во всех подобных случаях, или компилятор этот синтетический пример так оптимизировал? Ну и если у кого есть что почитать на эту тему - буду премного благодарен.
Это "почти поддерживается", но не всегда, не 100%. Напр MSVC в Debug вызовет все конструкторы. Также без объявления в левой части копирование состоится, напр
Код
C++ (Qt)
SomeLargeStuct s;
..
s = f1();   // здесь оптимизации не будет
 
Поэтому неконстантная ссылка или указатель надежнее


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Igors от Июль 12, 2016, 08:58
Не ну конешно это было бы здорово... только вот c/cpp и паскаль сильно разные вещи. Так, например, в паскале вызов ф-ии должен быть к чему-то приэссайнен, иначе код не скомпилируется.
Насколько я помню, начиная с Turbo-Pascal 4.0 (или 5.0) присвоение уже не было обязательным, можно было писать ReadKey() (а не ch:= ReadKey()). Ничего "здорового" в result не вижу, громоздко и неудобно

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


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Old от Июль 12, 2016, 09:12
Костыль - это как раз Move semantics, вынуждающая вводить "еще один" тип конструктора.
Костыль это передача не константной ссылки на объект. Который еще нужно сконструировать перед этим. А не все классы обязаны иметь конструктор по умолчанию.

А передавать большие структуры можно было уже давно. Посмотрите на "классы хранения" в Qt. А можно вернуть и shared_ptr на структуру.

Вместо этого достаточно было бы зарезервированного слова, по примеру this.
Что-то типа, например, result.
Вы сами возмущаетесь введением новых фич и тут же предлагаете свои. Вам нужно в комитет. :)


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Racheengel от Июль 12, 2016, 12:01
Костыль это передача не константной ссылки на объект. Который еще нужно сконструировать перед этим. А не все классы обязаны иметь конструктор по умолчанию.

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

Думайте о неконстантной ссылке на результат так же, как и о параметре-ссылке. Разницы для компилятора не должно быть принципиально никакой.


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Old от Июль 12, 2016, 12:39
Если предполагается возвращать объект "по значению", то он в любом случае обязан иметь конструктор, явно или не явно.
Ну так иметь конструктор != иметь конструктор по умолчанию.

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

Код:
class Foo
{
public:
    // Нет конструктора по умолчанию!
    Foo( const string &id );
};

Foo Bar::foo()
{
    Foo result( m_id_manager->get_id() );
    return result;
}

Как вы планируете создать Foo там, где про m_id_manager ничего не известно и взять id просто негде.
Начать добавлять костыли в виде невалидного (временного) id?
Или вытащить менеджер id в глобальное пространство, где он не нужен?
И это все для того, что бы можно было его возвращать? :)


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Racheengel от Июль 12, 2016, 12:53
Например, как то так:

Foo myFoo = Bar::foo(); // тут компилятор видит, что у нас возврат объекта по значению, и передает в метод ссылку на myFoo.

Foo Bar::foo()
{
    result.Foo( m_id_manager->get_id() ); // а тут мы вызываем конструктор для переданного myFoo
}



Название: Re: Протаскивание большой структуры через череду return
Отправлено: Old от Июль 12, 2016, 12:57
Например, как то так:
Это ваша идея, сейчас так нельзя. :)

Я лишь не разделяю мнение, что возврат структуры по не константной ссылке хорошее решение. :)
Хотя, сам иногда его использую. Я бы сказал, что это просто, но не хорошо. :)


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Racheengel от Июль 12, 2016, 13:07
Конечно, но это идея не моя, а товарища Вирта и компании, которые придумали Паскаль в 1970 ;)
И, честно говоря, я не услышал, почему это хуже, чем еще один типа конструктора ???

Offtop: Я слышал несколько раз мнения людей, которые довольно глубоко связаны с C++ и новым стандартов, по поводу новшеств - и они говорят, что язык просто искуственно подготавливают к вымиранию, т.к. в итоге он превратится в неподдерживаемую кашу и возникнет необходимость более гибкой и прозрачной, но в то же время производительной альтернативы.


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Old от Июль 12, 2016, 13:17
И, честно говоря, я не услышал, почему это хуже, чем еще один типа конструктора ???
Если вы про свою идею, то я пока не очень ее обдумывал, что бы сформировалось мнение. Если вы про возврат через ссылку, то я выше привел пример.

Offtop: Я слышал несколько раз мнения людей, которые довольно глубоко связаны с C++ и новым стандартов, по поводу новшеств - и они говорят, что язык просто искуственно подготавливают к вымиранию, т.к. в итоге он превратится в неподдерживаемую кашу и возникнет необходимость более гибкой и прозрачной, но в то же время производительной альтернативы.
Каждый имеет право на свое мнение. Сколько людей - столько и мнений. :)
Я тоже не использую все, что появилось в новом стандарте. Что то у меня прижилось, что-то нет. Но я не считаю, что все что у меня не прижилось - фигня, которую нужно срочно из стандарта убирать. :)


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Racheengel от Июль 12, 2016, 14:34
Если вы про возврат через ссылку, то я выше привел пример.

А чем приведенный пример противоречит идее возврата через ссылку?
Я привел вариант, как это могло бы быть использовано :)


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Old от Июль 12, 2016, 14:48
А чем приведенный пример противоречит идее возврата через ссылку?
Я привел вариант, как это могло бы быть использовано :)
Тем, что могут быть классы, объекты которых не удастся создать в вызывающей функции.  ::)


Название: Re: Протаскивание большой структуры через череду return
Отправлено: ssoft от Июль 13, 2016, 08:47
std::move семантика предназначена не только для оптимизации возврата значений из методов, для языка программирования она имеет больше фундаментальное значение, связанное с модельным понятием агрегации (это очень большая тема, поэтому не буду расписывать). Развитие семантики связано с объективными причинами ограниченности языка, каждое нововведение имеет теоретическое и практическое обоснование. Если разработчик считает, что оно ему не надо, то он может их и не использовать.


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Racheengel от Июль 13, 2016, 10:34
std::move семантика предназначена не только для оптимизации возврата значений из методов, для языка программирования она имеет больше фундаментальное значение, связанное с модельным понятием агрегации (это очень большая тема, поэтому не буду расписывать). Развитие семантики связано с объективными причинами ограниченности языка, каждое нововведение имеет теоретическое и практическое обоснование. Если разработчик считает, что оно ему не надо, то он может их и не использовать.

Обосновать при желании можно все, даже goto :)
Любой костыль можно объявить фичей и пропихнуть в стандарт, но костылем от этого оно быть не перестанет.
Думайте проще - разработчику надо решить задачу с минимальными затратами в минимальный срок, причем другие разработчики должны понять потом, что же было сделано. Итого - чем проще и прозрачнее синтаксис - тем лучше. Sugar на то и sugar :)


Название: Re: Протаскивание большой структуры через череду return
Отправлено: Racheengel от Июль 13, 2016, 10:35
Тем, что могут быть классы, объекты которых не удастся создать в вызывающей функции.  ::)

Но я же, вроде, написал, как можно бы было это обойти :)