|
|
|
Избыточное копирование объекта при реализации оператора умножения и оператора присваивания21.03.2016, 18:43. Показов 5235. Ответов 84
Метки нет (Все метки)
Есть класс работы с матрицами. Есть операция умножения матриц, описанная как оператор класса. В данном коротком примере я просто моделирую ситуацию. Реальное наполнение класса принципиальной разницы не играет, нужно просто понимать, что оператор умножения будет иметь большой код, оператор присваивания в реализации тоже будет не пустым. Эти места в примере засвечены комментариями
C++ #include <cstdio> class Matrix { private: // внутренние данные матрицы public: Matrix (); Matrix (const Matrix&); ~Matrix (); Matrix& operator=(const Matrix& a); Matrix operator*(const Matrix& a) const; }; Matrix::Matrix () { printf ("Matrix ()\n"); }; Matrix::Matrix (const Matrix&) { printf ("Matrix (const Matrix&)\n"); }; Matrix::~Matrix () { printf ("~Matrix ()\n"); }; Matrix& Matrix::operator=(const Matrix& a) { printf ("operator=(const Matrix&)\n"); // тут как бы код, копирущий данные из "a" в "this" return *this; } Matrix Matrix::operator*(const Matrix& a) const { printf ("operator*(const Matrix&)\n"); Matrix result; // тут как бы код, умножающий "a" и "this" и записывающий // данные в "result" return result; } Matrix a, b, c; int main (void) { printf ("------------------\n"); a = b * c; printf ("------------------\n"); } Code $ g++-4.8 t.cc $ ./a.out Matrix () Matrix () Matrix () ------------------ operator*(const Matrix&) Matrix () operator=(const Matrix&) ~Matrix () ------------------ ~Matrix () ~Matrix () ~Matrix () Посмотрим на ассемблерный код. Компилирую с опцией -fno-inline, чтобы оставаться с короткой программой и не разводить геморрой по борьбе с inline'ом со стороны компилятора. В общем случае тела операторов класса Matrix будут в отдельном файле и НЕ будут доступны для inline'а. Опцию -fno-exceptions подаю, чтобы было меньше мусора в коде Code $ g++ t.cc -O2 -fno-inline -fno-exceptions $ cat t.s ... leal -9(%ebp), %ebx subl $32, %esp movl %ebx, (%esp) movl $c, 8(%esp) movl $b, 4(%esp) call _ZN6MatrixmlERKS_ <- operator* subl $4, %esp movl %ebx, 4(%esp) movl $a, (%esp) call _ZN6MatrixaSERKS_ <- operator= ... C typedef struct { /* внутренности */ } Matrix; extern void operator_assign (Matrix *this, const Matrix *value); extern void operator_mul (Matrix *result, const Matrix *this, const Matrix *operand2); Matrix a, b, c; int main (void) { Matrix tmp; operator_mul (&tmp, &b, &c); /* tmp = b.operator* (c) */ operator_assign (&a, &tmp); /* a.operator= (tmp) */ } C operator_mul (&a, &b, &c); Вопрос. Как правильно написать текст на C++, чтобы получить код, эквивалентный вышеприведённой эффективной реализации на C? Оставаясь при этом в объёме стандарта C++98. Оставляя исходник в понятном и читабельном виде, без использования извращений типа семиэтажных шаблонов и т.п.
0
|
|
| 21.03.2016, 18:43 | |
|
Ответы с готовыми решениями:
84
Неправильная работа оператора присваивания после работы оператора суммирования От каких ошибок страхует Const при перегрузке оператора присваивания
|
|
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
|
||||||||||
| 21.03.2016, 18:58 | ||||||||||
|
то есть имеет место быть промежуточной переменной. проблема в наличии 3й промежуточной переменной: http://rextester.com/XMV97300
вот так и писать,как на си: за счет функции, которая принимает источник данных, и объект-назначения
4
|
||||||||||
|
161 / 153 / 92
Регистрация: 18.11.2015
Сообщений: 677
|
|
| 21.03.2016, 19:07 | |
|
С такими трудными приколами надо на стак оверфлоу какой-нибудь)
0
|
|
|
|
||
| 21.03.2016, 19:10 [ТС] | ||
|
Да, в просьбе забыл указать, что такой вариант с конструктором мне так же неинтересен, потому что подразумевает ОБЯЗАТЕЛЬНОЕ конструирование НОВОГО объекта, в то время как хочется записать результат умножения в уже существующий объект без дополнительных накладных расходов. Вариант, когда на каждый чих будет создаваться новая переменная выглядит скорее затычкой, чем реальным решением проблемы
C++ a = b * c; a = a * a;
0
|
||
|
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
|
|
| 21.03.2016, 19:20 | |
|
с++98 можно порешать только за счет эмуляции move
0
|
|
|
1379 / 406 / 144
Регистрация: 22.10.2014
Сообщений: 872
|
||||||||
| 21.03.2016, 19:41 | ||||||||
Добавлено через 7 минут Зная вашу дотошность, надеюсь подсказал почему так сделано
1
|
||||||||
|
|
|||
| 21.03.2016, 20:54 [ТС] | |||
|
Я, как афтор кода, знаю, что тут исключений не будет. Конкретно в запуске g++ была опция -fno-exceptions. Но дело ведь не в этом. Дело в том, что с точки зрения базовой концепции Си++ через реализацию операторов класса сия задача эффективно не реализуема, если я правильно понимаю. Я-то думал, что я что-то не так в реализации класса делаю. Судя по всему, тут просто принципиальная невозможность. Пичаль
0
|
|||
|
19500 / 10105 / 2461
Регистрация: 30.01.2014
Сообщений: 17,816
|
||
| 21.03.2016, 21:56 | ||
|
Кроме того, с применением move-семантики из С++11 эта задача также реализуется эффективно даже с одним объектом. Ну и проэмулировать move-семантику можно и на С++98, но это уже будет расцениваться как "использование извращений" (хотя реально кода там мало и его достаточно написать один раз и вынести в "библиотеку"). Так что я бы сказал, что задача не вообще не реализуется на С++, а не реализуется с учетом всех твоих конкретных требований и ограничений. Это все-таки разные вещи.
1
|
||
|
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
|
||||||||||||
| 21.03.2016, 23:27 | ||||||||||||
и объект будет создан, как он и указал. в случае RVO/NRVO оптимизаций создание объекта происходит "как бы снаружи" :
забивать на возможные эффекты конструктора копии. но объект в любом случае должен быть построен. потому что так указал программист. в случае, если снаружи присвоение, то выполнить перемещение уже не представляется возможным, и поэтому RVO/NRVO не сработают. если снаружи уже реально существующий объект ожидает присвоение, то и выполнено может быть только и только присвоение. а значит локальный объект должен быть построен. и после этого должна отработать операция присвоения. иначе нарушится логика, которую описал программист.
0
|
||||||||||||
|
1379 / 406 / 144
Регистрация: 22.10.2014
Сообщений: 872
|
||||||||||||||
| 22.03.2016, 00:46 | ||||||||||||||
*= не совсем извращенный способ, это законный/красивый оператор, который делает ровно то, что вы желаете - изменяет сам объект для которого применяется. Разве не эту цель вы преследуете?Также не стоит думать, что это все ужасно страшно скажется на производительности, поскольку С++ предполагает частое использование локальных переменных, а не указателей на глобальные, а для изменения непосредственно данной матрицы можно использовать опять же *=, который именно это и предполагает.Далее, мы видим "лишний" вывод "конструктора и деструктора и т.д.". А что мы ожидали? что компилятор просто так выбросит вывод? Выбросит код, который влияет на внешнюю глобальную переменную? Он не может этого сделать, но это не означает что там где-то в дали на самом деле выделилась память(в данном случае скорее всего означает, но стоит вернуть инлайн, включить lto и все изменится). Если компилятор сможет определить что он может безопасно оптимизировать код - он это сделает в меру своих сил, фишка С++ в том что мы сообщая больше информации и добавляя строгости коду передаем компилятору ответственность за оптимизации. И у него это вполне сносно выходит. Например : g++ -O2 -funroll-loops <и черт помнит какие ещё флаги там спрятаны, но без sse и подобного>
Функция getMap содержит 213 математических ассемблерных инструкций.(mov add mul) А Функция main - 164 инструкций ( это с учетом всего, в том числе единственный call cout'a)А в случае:
Доверяйте компилятору, дайте ему чуть поработать - это идеология плюсов. И с включенным lto, gcc также отлично инлайнит и оптимизирует между разными единицами трансляции. , ведь это разные исключения и мы можем их по разному обработать и при соответствующем исключении мы будем знать, что случилось с левым операндом. К списку исключений можно и потокобезопасность добавить, много чего поди можно добавить, но компиляторы пока такое оптимизировать не могут, лет через 100 может и смогут , а пока - таковы правила, но бояться этих правил не надо - см. выше пример.
0
|
||||||||||||||
|
|
||||||||
| 22.03.2016, 10:20 [ТС] | ||||||||
Просто вся неэффективность растёт из того, что для реализации операции "a = b * c" на Си я могу описать функцию с тремя параметрами (dst, src1, src2). А с "культурной" реализацией на C++ это будет два оператора mul и assign. Как только оператор mul оказался невидимым для компилятора, у него сразу же пропадает возможность соптимизировать оператор assign
0
|
||||||||
|
Неэпический
|
||||||
| 22.03.2016, 10:45 | ||||||
|
Глупенько, конечно, но:
0
|
||||||
|
2784 / 1937 / 570
Регистрация: 05.06.2014
Сообщений: 5,602
|
||
| 22.03.2016, 11:18 | ||
|
0
|
||
|
|
||||
| 22.03.2016, 13:22 [ТС] | ||||
|
Добавлено через 6 минут
0
|
||||
|
2784 / 1937 / 570
Регистрация: 05.06.2014
Сообщений: 5,602
|
|||
| 22.03.2016, 13:33 | |||
|
0
|
|||
|
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
|
|
| 22.03.2016, 13:36 | |
|
0
|
|
|
|
|||||
| 22.03.2016, 13:52 [ТС] | |||||
|
Добавлено через 4 минуты
0
|
|||||
|
1379 / 406 / 144
Регистрация: 22.10.2014
Сообщений: 872
|
||
| 22.03.2016, 14:26 | ||
|
Evg,
Можно сравнить С++ подход с ездой на мерседесе, а С подход с ездой от ЗАЗ'а до ферари, в зависимости от уровня разработчика. Если вы на самом деле такой супер разработчик и готовы потратить уйму времени, то скорее всего С подход будет производительнее, но на копейку.(ничего вам не напоминает ASM vs C ? )Касательно исключений и т.д., я вспомнил, это касается точек следования. Вызов оператора * - это точка следования. -> компилятор не может оставлять сайд эффекты, влияющие на левый операнд присваивания. И самое главное:
0
|
||
|
|
||||||||||||
| 22.03.2016, 14:40 | ||||||||||||
*=. Практика показывает, что оптимизированный код порой выглядит не совсем логично.
0
|
||||||||||||
| 22.03.2016, 14:40 | |
|
Помогаю со студенческими работами здесь
20
Какое значение получит переменная p при выполнении следующего оператора присваивания? Ошибка при выполнении оператора присваивания производного класса через указатель на базовый Ошибка при реализации перегрузки оператора <<
Переопределение оператора присваивания Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |
|
Новые блоги и статьи
|
|||
|
Как дизайн сайта влияет на конверсию: 7 решений, которые реально повышают заявки
Neotwalker 08.03.2026
Многие до сих пор воспринимают дизайн сайта как “красивую оболочку”. На практике всё иначе: дизайн напрямую влияет на то, оставит человек заявку или уйдёт через несколько секунд.
Даже если у вас. . .
|
Модульная разработка через nuget packages
DevAlt 07.03.2026
Сложившийся в . Net-среде способ разработки чаще всего предполагает
монорепозиторий в котором находятся все исходники.
При создании нового решения, мы просто добавляем нужные проекты
и имеем. . .
|
Модульный подход на примере F#
DevAlt 06.03.2026
В блоге дяди Боба наткнулся на такое определение:
В этой книге («Подход, основанный на вариантах использования») Ивар утверждает,
что архитектура программного обеспечения — это
структуры,. . .
|
Управление камерой с помощью скрипта OrbitControls.js на Three.js: Вращение, зум и панорамирование
8Observer8 05.03.2026
Содержание блога
Финальная демка в браузере работает на Desktop и мобильных браузерах. Итоговый код: orbit-controls-threejs-js. zip. Сканируйте QR-код на мобильном. Вращайте камеру одним пальцем,. . .
|
|
SDL3 для Web (WebAssembly): Синхронизация спрайтов SDL3 и тел Box2D
8Observer8 04.03.2026
Содержание блога
Финальная демка в браузере. Итоговый код: finish-sync-physics-sprites-sdl3-c. zip
На первой гифке отладочные линии отключены, а на второй включены:. . .
|
SDL3 для Web (WebAssembly): Идентификация объектов на Box2D v3 - использование userData и событий коллизий
8Observer8 02.03.2026
Содержание блога
Финальная демка в браузере. Итоговый код: finish-collision-events-sdl3-c. zip Сканируйте QR-код на мобильном и вы увидите, что появится джойстик для управления главным героем.
. . .
|
Реалии
Hrethgir 01.03.2026
Нет, я не закончил до сих пор симулятор. Эта задача сложнее. Не получилось уйти в плавсостав, но оно и к лучшему, возможно. Точнее получалось - но сварщиком в палубную команду, а это значит, в моём. . .
|
Ритм жизни
kumehtar 27.02.2026
Иногда приходится жить в ритме, где дел становится всё больше, а вовлечения в происходящее — всё меньше. Плотный график не даёт вниманию закрепиться ни на одном событии. Утро начинается с быстрых,. . .
|