При продвижении состояния объекта использование std::swap
хорошо работает для простых объектов и свопов указателей. Для других действий на месте, Boost.ScopeExit
работает довольно хорошо, но это не очень элегантно, если вы хотите поделиться обработчиками выходных данных по всем функциям. Есть ли собственный способ C++ 11 для достижения чего-то подобного Boost.ScopeExit
, но для лучшего использования кода?Использование std :: unique_ptr и lambdas для продвижения состояния объекта
ответ
(Ab) использовать пользовательские Deleters std::unique_ptr
как ScopeExitVisitor
или состояние сообщения. Прокрутите вниз до ~ 7-й строки main()
, чтобы узнать, как это фактически используется на сайте вызова. В следующем примере можно использовать либо std::function
, либо lambdas для Deleter
/ScopeExitVisitor
, для которых не требуются какие-либо параметры, и вложенный класс, если вам необходимо передать параметр в Deleter
/ScopeExitVisitor
.
#include <iostream>
#include <memory>
class A {
public:
using Type = A;
using Ptr = Type*;
using ScopeExitVisitorFunc = std::function<void(Ptr)>;
using ScopeExitVisitor = std::unique_ptr<Type, ScopeExitVisitorFunc>;
// Deleters that can change A's private members. Note: Even though these
// are used as std::unique_ptr<> Deleters, these Deleters don't delete
// since they are merely visitors and the unique_ptr calling this Deleter
// doesn't actually own the object (hence the label ScopeExitVisitor).
static void ScopeExitVisitorVar1(Ptr aPtr) {
std::cout << "Mutating " << aPtr << ".var1. Before: " << aPtr->var1;
++aPtr->var1;
std::cout << ", after: " << aPtr->var1 << "\n";
}
// ScopeExitVisitor accessing var2_, a private member.
static void ScopeExitVisitorVar2(Ptr aPtr) {
std::cout << "Mutating " << aPtr << ".var2. Before: " << aPtr->var2_;
++aPtr->var2_;
std::cout << ", after: " << aPtr->var2_ << "\n";
}
int var1 = 10;
int var2() const { return var2_; }
// Forward declare a class used as a closure to forward Deleter parameters
class ScopeExitVisitorParamVar2;
private:
int var2_ = 20;
};
// Define ScopeExitVisitor closure. Note: closures nested inside of class A
// still have access to private variables contained inside of A.
class A::ScopeExitVisitorParamVar2 {
public:
ScopeExitVisitorParamVar2(int incr) : incr_{incr} {}
void operator()(Ptr aPtr) {
std::cout << "Mutating " << aPtr << ".var2 by " << incr_ << ". Before: " << aPtr->var2_;
aPtr->var2_ += incr_;
std::cout << ", after: " << aPtr->var2_ << "\n";
}
private:
int incr_ = 0;
};
// Can also use lambdas, but in this case, you can't access private
// variables.
//
static auto changeStateVar1Handler = [](A::Ptr aPtr) {
std::cout << "Mutating " << aPtr << ".var1 " << aPtr->var1 << " before\n";
aPtr->var1 += 2;
};
int main() {
A a;
std::cout << "a: " << &a << "\n";
std::cout << "a.var1: " << a.var1 << "\n";
std::cout << "a.var2: " << a.var2() << "\n";
{ // Limit scope of the unique_ptr handlers. The stack is unwound in
// reverse order (i.e. Deleter var2 is executed before var1's Deleter).
A::ScopeExitVisitor scopeExitVisitorVar1(nullptr, A::ScopeExitVisitorVar1);
A::ScopeExitVisitor scopeExitVisitorVar1Lambda(&a, changeStateVar1Handler);
A::ScopeExitVisitor scopeExitVisitorVar2(&a, A::ScopeExitVisitorVar2);
A::ScopeExitVisitor scopeExitVisitorVar2Param(nullptr, A::ScopeExitVisitorParamVar2(5));
// Based on the control of a function and required set of ScopeExitVisitors that
// need to fire use release() or reset() to control which visitors are used.
// Imagine unwinding a failed but complex API call.
scopeExitVisitorVar1.reset(&a);
scopeExitVisitorVar2.release(); // Initialized in ctor. Use release() before reset().
scopeExitVisitorVar2.reset(&a);
scopeExitVisitorVar2Param.reset(&a);
std::cout << "a.var1: " << a.var1 << "\n";
std::cout << "a.var2: " << a.var2() << "\n";
std::cout << "a.var2: " << a.var2() << "\n";
}
std::cout << "a.var1: " << a.var1 << "\n";
std::cout << "a.var2: " << a.var2() << "\n";
}
Который производит:
a: 0x7fff5ebfc280
a.var1: 10
a.var2: 20
a.var1: 10
a.var2: 20
a.var2: 20
Mutating 0x7fff5ebfc280.var2 by 5. Before: 20, after: 25
Mutating 0x7fff5ebfc280.var2. Before: 25, after: 26
Mutating 0x7fff5ebfc280.var1 10 before
Mutating 0x7fff5ebfc280.var1. Before: 12, after: 13
a.var1: 13
a.var2: 26
С положительной стороны, этот прием хорош, потому что:
- Код, используемый в удаливших может получить доступ к закрытым переменным
- Deleter код может быть централизованно
- Использование lambdas по-прежнему возможно, хотя они могут получить доступ только к лобковым членам.
- Параметры могут быть переданы в Deleter с помощью вложенных классов, действующих как замыкания
- Не все
std::unique_ptr
экземпляры должны иметь объект, возложенные на них (например, это вполне приемлемо, чтобы оставить ненужные удаливших, установленные вnullptr
) - Изменение поведения в среда это просто вопрос вызова
reset()
илиrelease()
- Основываясь на том, как вы построить свой стек это возможно во время компиляции, чтобы изменить гарантии безопасности на объекте, когда объем
std::unique_ptr
(ов) выходит из области видимости
И наконец, используя Boost.ScopeExit
, вы можете переадресовывать вызовы в вспомогательную функцию или использовать условное значение, аналогичное тому, которое предлагает Boost.ScopeExit
с bool commit = ...;
. Нечто похожее:
#include <iostream>
#include <boost/scope_exit.hpp>
int main() {
bool commitVar1 = false;
bool commitVar2 = false;
BOOST_SCOPE_EXIT_ALL(&) {
if (commitVar1)
std::cout << "Committing var1\n"
if (commitVar2)
std::cout << "Committing var2\n"
};
commitVar1 = true;
}
и нет ничего плохого в том, что, но, как было предложено в оригинальный вопрос, как вы разделяете код без проксировании на вызов где-то еще? Используйте std::unique_ptr
'Deleters' как ScopeExitVisitors
.