Есть ли шаблон проектирования для идеальной пересылки, когда функция может выйти из строя и требуется повторная попытка вызывающей стороны? - PullRequest
0 голосов
/ 04 мая 2018

Мой вопрос касается идеальной пересылки, давайте сначала посмотрим на пример,

template<typename A, typename B>
void foo(A &&a, B &&b) {
   A internal_a = std::forward<A>(a);
   B internal_b = std::forward<B>(b);
}

Пока все хорошо. Я могу назвать это так,

X x;
Y y;
// do a lot of stuff on x, y
//.....
//.....
// we will use x,y in foo(x, y) only, so we can just move
foo(std::move(x), std::move(y));

Теперь, скажем, foo может потерпеть неудачу.

template<typename A, typename B>
bool foo(A &&a, B &&b) {
   A internal_a = std::forward<A>(a);
   B internal_b = std::forward<B>(b);
   // ...
   if (xxx) {
       return false;
   }
   // ...
   return true;
}

В звонящем

X x;
Y y;
// do a lot of stuff on x, y
//.....
//.....
// Oh my!
while (!foo(std::move(x), std::move(y)) ;

Это не сработает, потому что первый вызов foo переместит x и y.

Если вызывающая сторона не использует перемещение, то мы теряем все преимущества перемещения.

Мы можем сделать это:

X x;
Y y;
do {
   // do a lot of stuff on x, y
   //.....
   //.....
}
while (!foo(std::move(x), std::move(y)) ;

Это делает вызывающего абонента очень проблематичным.

Мы также можем сделать это,

template<typename A, typename B>
bool foo(A &&a, B &&b) {
   A internal_a = std::forward<A>(a);
   B internal_b = std::forward<B>(b);
   // ...
   if (xxx) {
       // This may move or copy, where the copy is uncessary
       a = std::forward<A>(internal_a);
       b = std::forward<B>(internal_b);
       return false;
   }
   // ...
   return true;
}

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

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

Пожалуйста, сообщите. Спасибо.

Ответы [ 2 ]

0 голосов
/ 07 мая 2018

Согласно всем комментариям, написать RAII, вероятно, является лучшим решением. Я публикую то, что написал (возможно, не во всех ситуациях), пожалуйста, прокомментируйте, если что-то не так.

template<typename A, typename B>
class move_guard {
private:
    bool release_;
    A a_;
    B b_;

    template<typename A_, typename B_>
    void move_internal_(A_ &a, B_ &&b) {
        a = std::move(b);
    }
    template<typename A_, typename B_>
    void move_internal_(const A_ &a, B_ &&b) {
    }

public:
    explicit move_guard(A a, B b, bool release) : a_(static_cast<A>(a)), b_(b), release_(release) {}
    move_guard(const move_guard &other) = delete;
    move_guard(move_guard &&other) : a_(static_cast<A>(other.a_)), b_(other.b_), release_(other.release_) {
        other.release_ = true;
    }
    move_guard &operator=(const move_guard&) = delete;
    move_guard &operator=(move_guard &&other) = delete;
    void release() { release_ = true; }

    ~move_guard() { 
        if (!release_) {
            // cannot just a_ = std::move(b_) because a_ might be const
            // lvalue ref, we cant pass the compilation although in runtime
            // it never will be called if a_ is const. 
            move_internal_(a_, b_);
        }
    }
};



// If a is an rvalue ref, return a guard that will move b back to a before destroyed.
// If a is a const-rvalue-ref, something is wrong, we don't allow this.
// If a is an lvalue ref (const or non-const), return a guard that does nothing.
template<typename A, typename B>
auto make_move_guard(A &&a, B &&b) {
    typedef decltype(a) atype;
    typedef decltype(b) btype;
    typedef typename std::remove_reference<atype>::type a_noref_type;
    typedef typename std::remove_reference<btype>::type b_noref_type;
    typedef typename std::remove_const<a_noref_type>::type a_base_type;
    typedef typename std::remove_const<b_noref_type>::type b_base_type;

    static_assert( !(std::is_rvalue_reference<atype>::value && std::is_const<a_noref_type>::value), 
        "It is non-sense to allow a to be a const-rvalue-reference.");
    static_assert(std::is_same<a_base_type, b_base_type>::value, 
        "The arguments must have the same base type.");
    static_assert(!std::is_rvalue_reference<btype>::value,
        "It is non-sense to allow b to be an rvalue-reference.");

    if (std::is_rvalue_reference<atype>::value) {
        return move_guard<atype &&, b_noref_type &>(std::forward<A>(a), b, false);
    } else {
        return move_guard<atype &&, b_noref_type &>(std::forward<A>(a), b, true);
    }
}

template<typename A, typename B>
bool foo(A &&a, B &&b) {
   A internal_a = std::forward<A>(a);
   B internal_b = std::forward<B>(b);

   auto guard_a = make_move_guard(std::forward<A>(a), internal_a);
   auto guard_b = make_move_guard(std::forward<B>(b), internal_b);

   if (xxx)
       return false;

   //if everything all right
   guard_a.release();
   guard_b.release();
   return true;
}
0 голосов
/ 04 мая 2018

Это строгая гарантия исключения, распространенная на тип возврата вместо пути выхода исключения.

Идея состоит в том, что при сбое все возвращается в исходное состояние.

Не то, чтобы обратное движение вперед не вперед назад; обратное копирование - это не копирование. По общему признанию обратное движение - движение назад. Вы бы использовали что-то другое, например, систему откатов транзакций, а не форвард в противоположном направлении. И делать это вручную плохо; используйте RAII. Наконец, вы захотите рассмотреть исключения и способы борьбы с операциями броска.

То, что вы хотите, действительно трудно без исключений или транзакционной памяти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...