Я'вэ узнал некоторые C++, и часто приходится возвращаться крупных объектов с функциями, которые создаются внутри функции. Я знаю, там'ы проходим по ссылке, возвращают указатель, и возвращает ссылку типа решений, но я'вэ тоже читал, что c++ компиляторы (и стандарт C++) позволяют возвращаемое значение оптимизации, которая позволяет избежать копирования этих крупных объектов по памяти, тем самым экономя время и память все это.
Теперь, я чувствую, что синтаксис является гораздо яснее, когда объект явно возвращаемое значение, и компилятор обычно использует РВО и сделать этот процесс более эффективным. Это плохая практика, чтобы полагаться на такую оптимизацию? Это делает код более ясным и удобочитаемым для пользователя, что очень важно, но я должен быть настороже, предполагая, компилятор будет ловить возможность РВО?
Это микро-оптимизации, или то, что я должен иметь в виду при проектировании мой код?
Использовать принцип наименьшего удивления.
Это вы и только вы, кто собирается использовать этот код, и вы уверены, что же вы в 3 года не собирается быть удивлены тем, что вы делаете?
Затем идите вперед.
Во всех остальных случаях используется стандартный способ, в противном случае вы и ваши коллеги собираетесь работать в трудно найти ошибки.
Например, мой коллега жаловался на мой код вызывает ошибки. Оказывается, он был выключен короткого замыкания булевой операции в его настройках компилятора. Я чуть не ударил его.
В данном конкретном случае, наверняка просто возвращать по значению.
РВО и NRVO известный и надежный оптимизаций, которые действительно должны быть сделаны любой приличный компилятор, даже в режиме c++03.
Семантика перемещения гарантировать, что объекты будут перемещаться из функции если (Н)РВО вовсе'т иметь место. Что's только полезно, если ваш объект использует динамические данные внутренне (типа СТД::вектор
делает), но то, что действительно должно быть в том случае, если это что большой ... переполнения стека-это риск с большой автоматические объекты.
В C++17 применяет РВО. Так Дон'т беспокоиться, он выиграл'т исчезают и останется только добить окончательно утвердился после компиляторы вверх-к-дата.
И в итоге, заставляя дополнительного динамического выделения возвращает указатель, или заставляя ваш результат должен быть по умолчанию-конструктивных точно так же вы можете передать его в качестве выходного параметра как некрасиво и не-идиоматические решения проблемы, вы, вероятно, не возникнет.
Просто написать код, который имеет смысл и благодарю создателей компиляторов для правильной оптимизации кода, что имеет смысл.
теперь, я чувствую, что синтаксис является гораздо яснее, когда объект явно возвращаемое значение, и компилятор обычно использует РВО и сделать этот процесс более эффективным. Это плохая практика, чтобы полагаться на такую оптимизацию? Это делает код более ясным и удобочитаемым для пользователя, что очень важно, но я должен быть настороже, предполагая, компилятор будет ловить возможность РВО?
Это'т некоторые малоизвестные, жеманный, микро-оптимизации, о которых можно прочитать в какой-нибудь маленький, маленький блог оборота и затем вы добираетесь, чтобы чувствовать себя умным и превосходные об использовании.
После c++11, РВО-это стандартный способ чтобы написать этот код. Это обычная, как ожидается, учил, говорилось в ходе переговоров, указанных в блоги, упоминаемые в стандарте, будет сообщено как ошибка компилятора, если не реализованы. В C++17, язык идет на один шаг дальше и мандаты копия Элизии в определенных сценариях.
Вы должны абсолютно положиться на эту оптимизацию.
Кроме того, возврат по значению как раз и приводит к массово проще код читать и управлять, чем код, который'возвращение по ссылке. Значение семантика-это мощная вещь, которая сама по себе может привести к более оптимизации.
Правильность кода, который вы пишете, должен никогда не зависеть от оптимизации. Он должен вывести правильный результат при выполнении на C++ "и виртуальную машину", что они используют в спецификации.
Однако то, что вы говорите о более такая эффективность вопрос. Ваш код работает лучше, если оптимизирован с РВО оптимизирующий компилятор. Что's прекрасно, по всем причинам, указанным в других ответах.
Однако, если вам требовать такой оптимизации (например, если конструктор копирования будет на самом деле привести ваш код с ошибкой), теперь вы're по прихоти компилятора.
Я думаю, что лучшим примером этого в моей собственной практике является оптимизация хвостовой вызов:
int sillyAdd(int a, int b)
{
if (b == 0)
return a;
return sillyAdd(a + 1, b - 1);
}
Это'ы глупый пример, но он показывает хвост вызова, когда функция вызывается рекурсивно в самом конце функции. В C++ виртуальная машина покажет, что этот код работает правильно, хотя я может вызвать небольшое замешательство, как почему мне мешал писать такое дополнение обычной в первую очередь. Однако в практических реализациях на C++, у нас есть стек, и он имеет ограниченное пространство. Если все сделано педантично, эта функция придется отодвинуть как минимум кадры стека в + 1
в стек, как это делает ее дополнение. Если я хочу, чтобы вычислить sillyAdd(5, 7), это не имеет большого значения. Если я хочу, чтобы вычислить
sillyAdd(0, 1000000000), я мог бы быть серьезные проблемы, приводящие к сайте StackOverflow (и не добрый).
Однако, мы видим, что когда мы достигнем этой последней линии возврата, мы'вновь действительно сделали все, что в текущем кадре стека. Мы не't действительно нужно, чтобы держать его вокруг. Оптимизация хвостовой вызов можно на "использовать" в существующий фрейм стека для следующей функции. Таким образом, нам нужен только 1 кадр стека, а не на уровне B+1
. (Мы все равно придется делать все эти глупые сложения и вычитания, но они Дон'т занять больше места.) В результате оптимизации получается код в:
int sillyAdd(int a, int b)
{
begin:
if (b == 0)
return a;
// return sillyAdd(a + 1, b - 1);
a = a + 1;
b = b - 1;
goto begin;
}
В некоторых языках, хвост вызова оптимизации явно требуется спецификацией. C++ - это не одно из них. Я не могу полагаться на C++ компиляторы, чтобы признать этот хвост назвать возможность оптимизации, если я иду конкретном случае. С моей версии Visual Studio, в версии ли оптимизация хвостовой вызов, но в отладочной версии не будет (по дизайну).
Таким образом, было бы для меня плохо зависит от возможности расчета sillyAdd(0, 1000000000)
.
На практике программы на языке C++ ожидают некоторые оптимизации компилятора.
Посмотрите, в частности, в стандартные заголовки стандартной контейнеры реализации. С ССЗ, Вы можете задать для предварительной обработки форма (г++ -С-Е
) и Гимпл внутреннее представление (г++ -fdump-дерево-Гимпл или Гимпл ССА с
-fdump-дерево-ССА) из исходных файлов (технически перевод единиц), используя контейнеры. Вы'll быть удивлены количеством оптимизации, которая выполняется (с
г++ -О2`). Поэтому конструкторы контейнеры полагаться на оптимизацию (и в большинстве случаев, конструктор из стандартной библиотеки C++ знает, что оптимизация будет, и писать контейнере реализации с ума; иногда он также напишите оптимизация в компиляторе дело с функционалом, необходимым после стандартной библиотеки C++).
На практике, это оптимизации компилятора, которые делают C++ и его стандартные контейнеры достаточно эффективным. Так что вы можете положиться на них.
И точно так же для случая РВО упомянули в своем вопросе.
Стандарт C++ была разработана (в частности, экспериментируя достаточно хорошей оптимизации, предлагая новые функции) для работы с возможными оптимизациями.
Например, рассмотрим программу ниже:
#include <algorithm>
#include <vector>
extern "C" bool all_positive(const std::vector<int>& v) {
return std::all_of(v.begin(), v.end(), [](int x){return x >0;});
}
скомпилировать его с G++ и-О3 -fverbose-ASM в -ых. Вы'll найти то, что создается функцией Дон'т запустить любой "вызов" автомат инструкция. Поэтому большинство шагов С++ (строительство закрытие лямбда, ее повторное применение, получение начало
и конец
итераторы и т. д...) были оптимизированы. Машинный код содержит только петли (которые явно не отображаются в исходном коде). Без такой оптимизации, в C++11 выиграл'т быть успешным.
(добавлено 31 декабря<суп>ст</с SUP> В 2017)
См. [CppCon 2017: Мэтт Godbolt “то, что мой компилятор сделал для меня в последнее время? Развинченном компилятор's с крышкой”][3] говорить.
Всякий раз, когда вы используете компилятор, понимание того, что он будет производить машины - или байт-код для вас. Это не гарантирует ничего о том, что сгенерированный код похож, за исключением того, что он будет внедрять в исходный код в соответствии со спецификацией языка. Обратите внимание, что эта гарантия не зависит от используемого уровня оптимизации, а так, в целом, нет оснований считать, что один выход как более 'право', чем другие.
Кроме того, в тех случаях, как РВО, где он указан в языке, казалось бы, бессмысленно идти из вашего пути, чтобы избежать использования его, особенно если это делает исходный код проще.
Много усилий вложено в создание компиляторов производят эффективный выход, и, очевидно, цель для тех возможностей, которые будут использоваться.
Там могут быть причины для использования неоптимизированный код (для отладки, например), но в случае, упомянутом в этом вопросе не представляется одному (и если ваш код не удается, только когда оптимизированные, и это не следствие какой-то особенностью устройства вы используете его, то есть где-то ошибка, и вряд ли в компиляторе.)
Я думаю, другие покрыты определенным углом о C++ и РВО хорошо. Вот более общий ответ:
Когда дело доходит до корректности, вы не должны полагаться на оптимизацию компилятора, или компилятор-специфическое поведение в целом. К счастью, вы не'т, кажется, делают это.
Когда речь заходит о производительности, вы должны полагаться на компилятор-определенное поведение в целом и оптимизаций, в частности. Соответствующий стандарту компилятор является бесплатным для компиляции кода в любом случае он хочет, как скомпилированный код ведет себя в соответствии со спецификацией языка. И я'м не в курсе каких-либо указаний на основной язык, который определяет, насколько быстро каждая операция должна быть.
<б>нет.</б>
Что's то, что я делаю все время. Если мне нужно получить доступ к произвольным 16-битный блок в памяти, я сделаю это
void *ptr = get_pointer();
uint16_t u16;
memcpy(&u16, ptr, sizeof(u16)); // ntohs omitted for simplicity
...и положиться на компилятор делает все возможное, чтобы оптимизировать этот кусок кода. Код работает на ARM, для i386, amd64 и практически на каждом архитектуры там. В теории, не-оптимизирующий компилятор действительно может называть функции memcpy, что приводит к очень плохой производительности, но это не проблема для меня, как я использовать оптимизацию компилятора.
Рассмотрим вариант:
void *ptr = get_pointer();
uint16_t *u16ptr = ptr;
uint16_t u16;
u16 = *u16ptr; // ntohs omitted for simplicity
Этот код не работает на машинах, которые требуют правильного выравнивания, если get_pointer()` возвращает внеблокового указатель. Также, там может быть сглаживание вопросы в альтернативной.
Разница между -O2 и -О0, когда с помощью функции memcpy` фокус-это здорово: 3.2 Гбит / выполнения контрольной суммы IP по сравнению с 67 Гбит / выполнения контрольной суммы IP. Более того, различие величины!
Иногда вам может понадобиться, чтобы помочь компилятору. Так, например, вместо того, чтобы полагаться на компилятор, чтобы развернуть петель, вы можете сделать это самостоятельно. Либо путем реализации известных на <а href="и https://en.wikipedia.org/wiki/Duff%27s_device">Дафф'устройства S</а> или более чистым способом.
Недостатком полагаться на оптимизацию компилятора заключается в том, что если вы запустите GDB для отладки кода, Вы можете обнаружить, что много был оптимизирован подальше. Поэтому, возможно, вам придется перекомпилировать с -О0, а значит, ваша работа будет полностью сосать при отладке. Я думаю, что это недостаток, стоит принимать, с учетом преимуществ оптимизирующие компиляторы.
Что бы вы ни делали, пожалуйста, убедитесь, что ваш путь-это на самом деле не определено поведение. Конечно, доступ к некоторым случайный блок памяти как 16-разрядное целое число является неопределенным поведением из-за сглаживания и выравнивания вопросов.
Оптимизации компилятора должен только влияет на производительность, а не результаты. Полагаться на оптимизацию компилятора, чтобы удовлетворить не функциональные требования-это не только разумно, это часто причина, почему один компилятор взял над другой.
Флаги, определяющие, каким образом конкретные операции выполняются (индекс или переполнения например), часто сосредоточенными в с оптимизаций, но разве'т быть. Они явно влияет на результаты расчетов.
Если оптимизация компилятор вызывает разные результаты, что это ошибка -- ошибка в компиляторе. Опираясь на баг в компиляторе, в долгосрочной перспективе ошибку ... что происходит, когда он получает фиксированный?
Используя флаги компилятора, которые изменяют порядок расчетов работа должна быть хорошо документирована, но используется по мере необходимости.
Все попытки эффективного кода, написанного на чем угодно, но сборка зависит очень сильно от оптимизации компилятора, начиная с самых элементарных, как для эффективного распределения регистров, чтобы избежать лишних разливов стек повсеместно и по крайней мере достаточно хороший, если не отличный, выбор инструкция. В противном случае мы'd быть обратно в 80-х, где мы должны были поставить "зарегистрироваться" советы повсюду и использовать минимальное количество переменных в функции, чтобы помочь архаичных компиляторы C или даже раньше, когда перейти
является полезным ветвления оптимизации.
Если мы не'т чувствую, что мы могли рассчитывать на наш оптимизатор'ы умение оптимизировать свой код, мы'd все еще быть кодирование критических путей выполнения в агрегате.
Это's действительно вопрос того, насколько надежно, вы чувствуете оптимизации может быть сделано, что лучше разберется профилирования и ищет возможности компиляторов у вас есть и, возможно, даже разборки если там'ы точке доступа вы можете'т выяснить, где компилятор, кажется, не удалось совершить очевидную оптимизацию.
РВО-это то, что была вокруг в течение веков, и, по крайней мере, кроме очень сложных случаев, является то, что компиляторы были надежно применяя для возрастов. Это'ы, безусловно, не стоит работать вокруг проблемы, которая не'т существуют.
ERR на стороне полагаться на оптимизатор, не опасаясь, что она
Наоборот, я'd не сказать, ошибаться в сторону слишком полагаясь на компилятор оптимизации, чем слишком мало, и это предложение исходит от парня, который работает в очень критических областях, где эффективность, надежность, и качество среди клиентов-это все одно гигантское пятно. Я'д, А ты слишком уверенно полагаться на оптимизатор и найти какую-то непонятную пограничных случаях, когда вы слишком сильно не полагаться слишком мало опирался и просто кодирование от суеверных страхов все время для остальной части вашей жизни. Что'мр не менее вы потянувшись за профайлер и расследует должным образом, если вещи Дон'т выполнять как быстро, как они должны и получить ценные знания, а не суеверия, по пути.
Вы'повторно делать хорошо, чтобы опереться на оптимизатора. Держите его вверх. Дон'т, как стать тем парнем, который начинается прямо с просьбой рядный каждая функция вызывается в цикле, еще до профилирования из ошибочной страха оптимизатор's недостатков.
Профайлинг
Профайлинг-это действительно кольцо, но окончательный ответ на ваш вопрос. Проблема новичков, жаждущих писать эффективный код, которым зачастую сложно не то, что оптимизировать, это's что не для оптимизации, потому что они развивают все виды ошибочных догадках о неэффективности, что, хотя по-человечески понятный, являются весьма неправильно. Разработки опыт работы с профайлером начнет реально давать вам правильно оценивать не только свои компиляторы' возможности оптимизации, которые можно уверенно опереться, но и возможности (а также ограничения) вашего оборудования. Там'ы, пожалуй, даже большее значение в профилирование в изучении того, что было'т стоит оптимизировать, чем изучение того, что было.
Программное обеспечение может быть написано на C++ на различных платформах и для много различных целей.
Это полностью зависит от цели программы. Он должен быть легкий для поддержания, расширения, патч, рефакторинг Эт.С. или другие вещи, более важные, такие как производительность, стоимость и совместимость с конкретным оборудованием или время, необходимое для разработки.
Я думаю, что скучно отвечать на это: 'это зависит'.
Это плохая практика, чтобы написать код, который зависит от оптимизации компилятора, которые могут быть отключены и где данная уязвимость не документированы и где код в вопрос не полностью протестирован, так что, если он сделал перерыв, вы'буду знать это? Наверное.
Это плохая практика, чтобы написать код, который основывается на оптимизации компилятора, что не может быть выключен, что документальное а - тестами? Может и нет.
Если нет больше вы не договариваете, это плохая практика, но не по той причине, что вы предлагаете.
Возможно, в отличие от других языков, которые вы использовали ранее, возвращают значение объекта в C++ дает копию объекта. Если вы измените объект, вы изменяете другой объект. То есть, если у меня есть объект obj а; а.х=1; " и " объект obj Б = а;, то я б.х += 2; б.Ф();
, то `есть.х по-прежнему равна 1, а не 3.
Так нет, используя объект как значение, а не ссылка или указатель не обеспечивает такую же функциональность, и вы могли бы в конечном итоге с ошибками в программном обеспечении.
Возможно, вы знаете это, и это не окажет отрицательного влияния на конкретный случай использования. Однако, исходя из формулировки вашего вопроса, похоже, что вы могли не быть в курсе различие; такие формулировки, как "создать объект в функции.&и"
на "создать объект в функции" и звучит как новый параметр obj; где-то "возврата объекта по значению", которая звучит как
в obj а; возвращать;`
В obj а;
и OBJ В* а = новый параметр obj;
очень, очень разные вещи; первое может привести к повреждению памяти, если не правильно использовать и понимать, и последнее может привести к утечки памяти, если не правильно использовать и понимать.
Питер Б абсолютно правильно, предложив наименьшего удивления.
Чтобы ответить на ваш конкретный вопрос, что это (скорее всего) означает в C++ является то, что вы должны вернуть СТД::unique_ptr не на построенный объект.
Причина в том, что это яснее, для C++ разработчик как к тому, что's идя дальше.
Хотя ваш подход будет скорее всего работать, вы'вновь эффективно сигнализировать о том, что объект является маленький тип значения, когда, по сути, это'т. На вершине, что, вы'вэ выбросить любую возможность для забора интерфейс. Это может быть хорошо для ваших текущих целей, но часто бывает очень полезно при работе с матрицами.
Я понимаю, что если вы'вэ пришел из других языков, все сигилы могут быть изначально сбивает с толку. Но будьте осторожны, чтобы не предположить, что, не используя их, вы делаете свой код более понятным. На практике, наоборот, скорее всего, чтобы быть правдой.