#c
#c
Вопрос:
Я пытаюсь гарантировать, что объект, обернутый shared_ptr
, работает до тех пор, пока выполняется функция, передавая ее как значение. Однако внутри функции объект вообще не используется, поэтому я просто хочу использовать его для «закрепления»:
void doSomething(std::shared_ptr<Foo>) {
// Perform some operations unrelated to the passed shared_ptr.
}
int main() {
auto myFoo{std::make_shared<Foo>()};
doSomething(std::move(myFoo)); // Is 'myFoo' kept alive until doSomething returns?
return 0;
}
Я проверил поведение на разных уровнях оптимизации (GCC), и кажется, что оно работает так, как задумано, однако я не знаю, может ли компилятор по-прежнему оптимизировать его в определенных сценариях.
Комментарии:
1. Компилятору всегда разрешено изменять порядок всех действий, пока код работает «как если бы» написанный. Таким образом, у вашего кода есть хорошие шансы получить только «return 0» из main здесь!
2. Вы правы — это очень продуманный пример. Давайте предположим, что есть какой-то побочный эффект с точки зрения
doSomething()
, а также деструкторFoo
выполняет некоторыеcout
действия. Тогда я ожидал бы, что печать функции появится раньше, чем та, которая поступает от деструктора.
Ответ №1:
Вам не нужно беспокоиться — время жизни аргумента функции на сайте вызова гарантированно сохраняется при вызове функции. (Вот почему такие вещи, как foo(s.c_str())
для std::string s
работы.)
Компилятору не разрешается нарушать это правило при условии гибкости правила как если бы.
Комментарии:
1. Хорошо — и это также применимо, если параметр функции вообще не используется в самой функции, верно?
2. @Phlar: Абсолютно. Да, компилятору разрешено выполнять оптимизацию, но это не должно изменять поведение программы.
3. Есть одно интересное исключение из правила как будто , которое касается исключения конструктора. Там компилятор может изменить поведение программы, если у конструктора есть побочные эффекты, поскольку побочные эффекты не произойдут, если конструктор исключен.
4. @Eljay: А также некоторые выделения в новом выражении начиная с C 14.
Ответ №2:
Это очень сильно зависит от того, как на самом деле будет выглядеть тело doSomething
и Foo
. Например, рассмотрим следующий пример:
struct X
{
~X() { std::cout << "2"; };
};
void f(std::shared_ptr<X>) { std::cout << "1"; }
int main()
{
auto p = std::make_shared<X>();
f(std::move(p));
}
Эта программа имеет тот же наблюдаемый эффект, что и:
int main()
{
std::cout << "12";
}
и порядок "12"
гарантирован. Итак, в сгенерированной сборке может не использоваться общий указатель. Однако большинство компиляторов, скорее всего, не будут выполнять такую агрессивную оптимизацию, поскольку внутри задействованы динамические выделения памяти и вызовы виртуальных функций, что не так просто оптимизировать.
Ответ №3:
Компилятор мог бы оптимизировать копирование объекта в аргумент функции, если функция встроена и если копирование не имеет побочных эффектов.
Копирование shared_ptr
увеличивает его количество ссылок, поэтому у него есть побочные эффекты, поэтому компилятор не может его оптимизировать (если только компилятор не может доказать себе, что отсутствие изменения количества ссылок не влияет на программу).
Комментарии:
1. Но что, если количество ссылок не участвует в наблюдаемом эффекте программы? Это своего рода другая тема, но
int main() { auto ptr = std::make_shared<int>(); }
имеет тот же эффект, что и пустойmain
в соответствии с правилом «как если бы».2. @DanielLangr возможно, я не уверен, что оптимизаторы могут удалять атомарные приращения / декременты?
3.
std::atomic
не наблюдается, ноvolatile
из правила as_if .