Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/158.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 当函数可能失败并需要调用方重试时,是否存在完美转发的设计模式?_C++_C++11 - Fatal编程技术网

C++ 当函数可能失败并需要调用方重试时,是否存在完美转发的设计模式?

C++ 当函数可能失败并需要调用方重试时,是否存在完美转发的设计模式?,c++,c++11,C++,C++11,我的问题是关于完美转发,让我们先看一个例子 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); } 现在,假设foo可能会失败 template<typename A, typename B> bool

我的问题是关于完美转发,让我们先看一个例子

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);
}
现在,假设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;
}
这不起作用,因为对foo的第一次调用将移动x和y

如果调用方不使用move,那么我们将失去move的所有优势

我们可以这样做:

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;
}
模板
布尔富(A&A,B&B){
A内部_A=std::forward(A);
B内部_B=标准::正向(B);
// ...
if(xxx){
//这可以移动或复制,如果复制是不必要的
a=标准::正向(内部_a);
b=标准::正向(内部_b);
返回false;
}
// ...
返回true;
}
如果调用者确实在移动,那么这可能会很好地工作。如果调用者不移动,则会产生不必要的副本

可能我需要的是确切地知道移动或复制发生在完美转发中,如果是移动,我们必须向后移动以使调用方更容易


请告知。谢谢。

这是扩展到返回类型而不是异常退出路径的强异常保证

这个想法是,一旦失败,一切都会回到初始状态

并不是说前进的倒数不是前进的倒数;复制的反面是noop而不是copy。诚然,移动的反面是向后移动。你应该使用一些不同的东西,比如跨分泌回滚系统,而不是相反方向的前进。手工操作是不好的;使用RAII。最后,您将要考虑异常和如何处理投掷移动操作。


如果没有异常内存或事务性内存,您想要的是非常困难的。

根据所有评论,编写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;
}
模板
阶级运动护卫{
私人:
布尔释放;
A A_;
B B_;
模板
无效移动\u内部\u(A\A、B\U和B){
a=标准::移动(b);
}
模板
无效移动\u内部\u(常量A\A、B\U和B){
}
公众:
显式移动保护(A A,B B,bool释放):A(静态施法(A)),B(B),释放(释放){}
移动保护(常量移动保护和其他)=删除;
移动保护(移动保护和其他):a(静态保护(其他.a)),b(其他.b),释放(其他.release){
other.release=真;
}
move\u guard&operator=(const move\u guard&)=删除;
移动防护装置和操作员=(移动防护装置和其他)=删除;
void release(){release\=true;}
~move_guard(){
如果(!释放){
//不能只移动a_uu=std::move(b_u),因为a_u可能是常量
//左值ref,虽然在运行时,我们无法通过编译
//如果a_u是常量,则永远不会调用它。
移动内部(a,b);
}
}
};
//如果a是右值参考,返回一个将b移回a的守卫,然后销毁。
//如果a是常量值ref,则表示有问题,我们不允许这样做。
//如果a是左值ref(常量或非常量),则返回一个不执行任何操作的保护。
模板
自动移动防护装置(A&A、B&B){
typedef decltype(a)atype;
typedef decltype(b)b类型;
typedef typename std::remove_reference::type a_noref_type;
typedef typename std::remove_reference::type b_noref_type;
typedef typename std::remove_const::type a_base_type;
typedef typename std::remove_const::type b_base_type;
静态断言(!(std::is_-rvalue_-reference::value&&std::is_-const::value),
“允许a作为常量值引用是没有意义的。”);
静态断言(std::is_same::value,
“参数必须具有相同的基类型。”);
静态断言(!std::is_-rvalue_-reference::value,
“允许b作为右值引用是没有意义的。”);
if(std::is_-rvalue_-reference::value){
返回移动保护(标准::前进(a),b,假);
}否则{
返回移动保护(标准::前进(a),b,正确);
}
}
模板
布尔富(A&A,B&B){
A内部_A=std::forward(A);
B内部_B=标准::正向(B);
自动保护装置=使保护装置移动(标准:前进(a),内部保护装置);
自动防护装置=使防护装置移动(标准:前进(b),内部防护装置);
if(xxx)
返回false;
//如果一切都好的话
防护装置a.释放装置();
防护装置松开();
返回true;
}

在这种情况下,最简单的解决方案就是不要移动任何东西。传递一个对函数所需内容的引用,打包到结构或类中。函数通过深入结构访问所有内容。如果需要再次调用该函数,请再次传递相同的引用。一切都在你留下的地方,保存完好。因为C++现在有“完美的转发”并不意味着它必须每次使用,只是为了使用它。嗯,我看到的第一件事是,你可能不想移动,直到你看到你的条件得到满足。另一种方法是,我认为从技术上讲,您可以将变量移回,因为您将从临时变量中初始化一个(现在是未初始化的对象,从它移走之后)。如果函数
foo
修改了它的局部变量,那么将它们移回调用方的参数有什么好处?如果它没有修改它们,为什么在那之前它需要这些局部变量?@aschepler在我的用例中,局部变量不是真正的“局部”,它是一个用于快速数据检索的内存存储。可以通过多个线程(读和写)访问数据存储。首先将数据插入某个临时结构(必须移动或复制)是有意义的,但后来发现该结构与其他线程冲突,必须宣布插入失败(必须删除该结构)。在这种情况下,调用方必须重试。
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;
}
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;
}