C ++ 11 — Есть ли способ продлить время жизни временного объекта в C ++?

Я написал защиту области, которая сбрасывает значение при выходе из области:

template <class T>
struct ResetGuard
{
T old_value;
T& obj_to_reset;
ResetGuard(T& obj_to_reset, const T& new_value) :
old_value(obj_to_reset),
obj_to_reset(obj_to_reset)
{
obj_to_reset = new_value;
}

~ResetGuard() { obj_to_reset = old_value; }
};

Когда эта функция защиты области возвращается из функции, есть ли способ предотвратить немедленное уничтожение защиты области действия, если она не была сохранена?

Например:

int GLOBAL_VALUE = 0;
ResetGuard<int> temporarily_set_global_value(int new_val) {
return { GLOBAL_VALUE, new_val }; //updates the global variable
}
void foo() {
//Ideally, someone calling this function
//Wouldn't have to save the returned value to a local variable
temporarily_set_global_value(15);
std::cout << "GLOBAL_VALUE is " << GLOBAL_VALUE << std::endl;
}

Как это написано сейчас, любой, кто вызывает одну из функций, должен помнить, чтобы всегда сохранять ResetGuard в локальную переменную, иначе он немедленно сбросит значение.

Я пишу библиотеку для форматирования и управления строками. У меня есть одна глобальная переменная, управляющая форматированием чисел с плавающей запятой. я знать что глобальные переменные, как правило, ужасная идея, но, пожалуйста, потерпите меня.

Я принял решение использовать глобальную переменную осторожно. Альтернативой использованию глобальной переменной будет передача объекта, содержащего спецификацию форматирования. Эта опция в конечном итоге оказалась недостижимой: моя библиотека предназначена для работы с любыми объектами, которые обеспечивают неявное преобразование в std::string, Нет способа передать параметры форматирования (или любые другие параметры) в функцию неявного преобразования. Следовательно, мне пришлось прибегнуть к использованию глобальной переменной.

2

Решение

Есть ли способ продлить время жизни временного объекта в C ++?

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

#define CONCAT(a, b) a##b
#define SCOPED_GLOBAL_VALUE(x) \
auto&& CONCAT(_unused, __COUNTER__) __attribute__((unused)) = temporarily_set_global_value(x)

Так что теперь, когда пользователи пишут:

SCOPED_GLOBAL_VALUE(15);

Они получают переменную бесплатно с желаемой выразительностью.

Но, конечно, есть одна оговорка. Поскольку мы генерируем имя переменной с помощью препроцессора, мы не можем использовать этот макрос во встроенной функции. Если мы это сделаем, мы нарушим одно определение правила. Так что это вещь для рассмотрения.

Лично я бы не стал это подчеркивать. Это общая идиома требовать именованный объект RAII (думаю, lock_guard), поэтому просто представить правильно названную функцию было бы просто для любого опытного программиста C ++.

1

Другие решения

Прежде чем ответить на ваш вопрос, я хотел бы предоставить правильный способ решения этой проблемы в C ++.

template <class T>
struct [[nodiscard]] ResetGuard
{
T old_value;
T& obj_to_reset;
bool enabled{true};

ResetGuard(T& obj_to_reset, const T& new_value) :
old_value(obj_to_reset),
obj_to_reset(obj_to_reset)
{
obj_to_reset = new_value;
}

ResetGuard(ResetGuard &&rhs)
: old_value(rhs.old_value)
, obj_to_reset(obj_to_reset)
{
rhs.enabled = false;
}
~ResetGuard()
{
if (enabled)
obj_to_reset = old_value;
}
ResetGuard(const ResetGuard &) = delete;
ResetGuard &operator=(const ResetGuard &) = delete;
ResetGuard &operator=(ResetGuard &&) = delete;
};

void foo() {
auto guard = temporarily_set_global_value(15);
std::cout << "GLOBAL_VALUE is " << GLOBAL_VALUE << std::endl;
}

Приведенный выше код содержит несколько интересных элементов:

  • [[Nodiscard]] предотвратить создание временных без создания переменной, чтобы обеспечить область
  • Участник включен: предотвращение Dtor временного, чтобы иметь побочный эффект
  • Конструктор Move: Конструктор Move позволяет перемещать ResetGuard в другую область с правильной обработкой. В этом случае отключение старого ResetGuard

В качестве дополнительного примечания я хотел бы обратить внимание на расширение C ++ 17 (ранее разрешенная оптимизация), которое называется Гарантированное копирование / перемещение Elision. Это гарантирует, что на практике не будет никаких дополнительных временных экземпляров.

Вернуться к вашему вопросу: Есть ли способ продлить время жизни временного объекта в C ++?

Да, спасибо N0345 (Предложение от 1993 г.). Это предложение позволяет продлить временное, захватив его по постоянной ссылке.

const auto &guard = temporarily_set_global_value(15);

Однако мне неясно, сколько всего у вас будет таких случаев. Однако, если вы используете решение с конструктором перемещения, это больше не проблема. Более того, когда вы используете оптимизацию компилятора, эта функция может быть встроена при реализации в заголовке. Это может устранить все копии.

1

Извините за мой предыдущий ответ, ребята, что было Я думаю? Я должен был прочитать вопрос правильно.

Так что, конечно, foo() должен вернуть ваш ResetGuard объект, чтобы продлить срок его службы, и это хорошо, неплохая вещь

Во-первых, это вряд ли обременительно для звонящего. В конце концов, все, что он / она должен сделать, это:

auto rg = foo ();

В качестве потенциального абонента foo() У меня не было бы абсолютно никаких проблем с этим, и @ melpomene’s отлично предложение в комментариях выше ([[nodiscard]]) можно использовать для гарантии того, что звонящие не забудут это сделать.

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

Что касается других ответов здесь, я бы определенно не скрывал все это в макросе, потому что он скрывает важную часть информации от потенциальных абонентов foo(), Вместо этого я бы использовал [[nodiscard]] напомнить им об их обязанностях и оставить все как есть.

[Редактировать]

Сейчас я провел немного времени в Wandbox, дорабатывая код, чтобы добавить полный набор рекомендуемых конструкторов / операторов присваивания и продемонстрировать использование [[nodiscard]], что для меня является находкой дня.

Во-первых, модифицированный класс, сделанный так (как я полагаю) те, кто знает, рекомендуют. Я особенно вижу важность определения правильного конструктора перемещения (просто подумайте о тонких ошибках, с которыми вы можете столкнуться, если не сделаете этого). Ущипнул некоторые вещи (= delete) от JVApen, выглядит мудрым для меня, TU JV.

#include <iostream>
#include <assert.h>

#define INCLUDE_COPY_MOVE_SWAP_STUFF

template <class T> class [[nodiscard]] ResetGuard
{
public:
ResetGuard (T& obj_to_reset, const T& new_value) : old_value (obj_to_reset), obj_to_reset (obj_to_reset)
{
obj_to_reset = new_value;
}

#ifdef INCLUDE_COPY_MOVE_SWAP_STUFF
ResetGuard (const ResetGuard& copy_from) = delete;
ResetGuard &operator= (const ResetGuard& copy_assign_from) = delete;
ResetGuard &operator= (ResetGuard&& move_assign_from) = delete;

ResetGuard (ResetGuard&& move_from) : old_value (move_from.old_value), obj_to_reset (move_from.obj_to_reset)
{
assert (!move_from.defunct);
move_from.defunct = true;
}
#endif

~ResetGuard()
{
if (!defunct)
obj_to_reset = old_value;
}

private:
T old_value;
T& obj_to_reset;
bool defunct = false;
};

Закомментируйте #define INCLUDE_COPY_MOVE_SWAP_STUFF чтобы увидеть предупреждение компилятора, которое вы получите, если не будете делать все то, что должны.

Тестовая программа:

int GLOBAL_VALUE = 0;

ResetGuard<int> temporarily_set_global_value (int new_val)
{
return { GLOBAL_VALUE, new_val }; // updates GLOBAL_VALUE
}

void bad_foo()
{
temporarily_set_global_value (15);
std::cout << "GLOBAL_VALUE in bad_foo () is " << GLOBAL_VALUE << std::endl;
}

void good_foo()
{
auto rg = temporarily_set_global_value (15);
std::cout << "GLOBAL_VALUE in good_foo () is " << GLOBAL_VALUE << std::endl;
}

auto better_foo()
{
auto rg = temporarily_set_global_value (15);
std::cout << "GLOBAL_VALUE in better_foo () is " << GLOBAL_VALUE << std::endl;
return rg;
}

int main ()
{
bad_foo ();
good_foo ();
std::cout << "GLOBAL_VALUE after good_foo () returns is " << GLOBAL_VALUE << std::endl;

{
auto rg = better_foo ();
std::cout << "GLOBAL_VALUE after better_foo () returns is " << GLOBAL_VALUE << std::endl;

{
auto rg_moved = std::move (rg);
std::cout << "GLOBAL_VALUE after ResetGuard moved is " << GLOBAL_VALUE << std::endl;
}

std::cout << "GLOBAL_VALUE after ResetGuard moved to goes out of scope is " << GLOBAL_VALUE << std::endl;
GLOBAL_VALUE = 42;
}

std::cout << "GLOBAL_VALUE after ResetGuard moved from goes out of scope is " << GLOBAL_VALUE << std::endl;
}

Выход компилятора:

prog.cc: In function 'void bad_foo()':
prog.cc:47:38: warning: ignoring returned value of type 'ResetGuard<int>', declared with attribute nodiscard [-Wunused-result]
temporarily_set_global_value (15);
^
prog.cc:40:17: note: in call to 'ResetGuard<int> temporarily_set_global_value(int)', declared here
ResetGuard<int> temporarily_set_global_value (int new_val)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
prog.cc:6:40: note: 'ResetGuard<int>' declared here
template <class T> class [[nodiscard]] ResetGuard
^~~~~~~~~~

Выход программы:

GLOBAL_VALUE in bad_foo () is 0
GLOBAL_VALUE in good_foo () is 15
GLOBAL_VALUE after good_foo () returns is 0
GLOBAL_VALUE in better_foo () is 15
GLOBAL_VALUE after better_foo () returns is 15
GLOBAL_VALUE after ResetGuard moved is 15
GLOBAL_VALUE after ResetGuard moved to goes out of scope is 0
GLOBAL_VALUE after ResetGuard moved from goes out of scope is 42

Так что у вас есть это. Если вы делаете все то, что должны делать (и я надеюсь, что у меня есть!), То все работает просто отлично, и все это хорошо и эффективно благодаря RVO и гарантированному разрешению копирования, так что об этом также не нужно беспокоиться.

Живая демо.

1
По вопросам рекламы [email protected]