Right Value Series 5: Abnormal and secure transfer

Source: Internet
Author: User

From: http://cpp-next.com/archive/2009/10/exceptionally-moving/

Welcome to the fifth article in the series about efficient value types in C ++. In the previous article, we stayed in the constant search for the optimal implementation of transfer assignment. Today, we are going to find a path through this "Move City", where the most common type of conflict may be surprising.

In the previous article, we saw that by providing "transfer Permissions", the same piece of code can be used for both the transfer type and the non-transfer type, transfer optimization should be used whenever possible. This "transfer when possible and copy when necessary" method is useful for code optimization and is also compatible with the old types without the transfer constructor. However, operations that provide strong exception guarantee increase the burden.

High exception guarantee and high exception requirements

To implement a strong exception, you must divide all the steps of an operation into two types:

  1. It is possible to throw an exception but does not include any irreversible changes.
  2. Operations that may be irreversible but do not throw exceptions

High exceptions are guaranteed to depend on the classification of operations in each step.

If we divide all actions into these two categories and make sure that all 1st actions occur before 2nd actions, nothing will happen to us. There is a typical example in C ++ 03. When vector: Reserve () needs to allocate memory for new elements:

void reserve(size_type n){    if (n > this->capacity())    {        pointer new_begin = this->allocate( n );        size_type s = this->size(), i = 0;        try        {            // copy to new storage: can throw; doesn't modify *this            for (;i < s; ++i)                 new ((void*)(new_begin + i)) value_type( (*this)[i] );        }        catch(...)        {            while (i > 0)                 // clean up new elements               (new_begin + --i)->~value_type();             this->deallocate( new_begin );    // release storage            throw;        }        // -------- irreversible mutation starts here -----------        this->deallocate( this->begin_ );        this->begin_ = new_begin;        this->end_ = new_begin + s;        this->cap_ = new_begin + n;    }}

If the transfer operation is supported, we need to add an explicit call to STD: Move in the try block and change the loop:

for (;i < s; ++i)     new ((void*)(new_begin + i)) value_type( std::move( (*this)[i] ) );

In this change, it is interesting that if value_type supports transfer, * This will be rewritten in the loop (explicitly transfer requests from the left value, is a logic rewrite operation ).

Now, if the transfer operation throws an exception, this loop will produce irreversible changes, because to roll back a partially completed loop, more transfer operations are required. Therefore, to ensure strong exception guarantee when value_type supports transfer, its transfer constructor must not be thrown.

A throw transfer operation may not be able to implement a rollback with no potential to be thrown again.

Result

In the draft C ++ 0x standard, it is basically against the writable transfer constructor. We recommend that you follow this rule. However, the transfer constructor must not throw this rule, which is not always so easy to follow. Take STD: pair <STD: String, usertype> as an example. Here, usertype is a type with a replica constructor that can be throttled. In C ++ 03, this type is correct and can be used in STD: vector. However, in C ++ 0x, STD: String carries a transfer constructor, and STD: pair also has:

template <class FirstType, class SecondType>pair<FirstType,SecondType>::pair(pair&& x)  : first(std::move(x.first))  , second(std::move(x.second)){}

There is a problem here. The second type is usertype. It does not have a transfer constructor, which means that the construction of second is a copy constructor (which may throw) instead of a transfer constructor. Therefore, pair <STD: String, usertype> provides a writable transfer constructor, which cannot be used in STD: vector without disrupting the strong exception.

Today, this means that we need something similar to the following code to make pair available.

template pair(pair&& rhs  , typename enable_if<                 // Undocumented optional        mpl::and_<                      // argument, not part of the            boost::has_nothrow_move // public interface of pair.          , boost::has_nothrow_move        >     >::type* = 0)  : first(std::move(rhs.first)),    second(std::move(rhs.second)){};

Enable_if can cause the constructor to "disappear" Unless has_nothrow_move is true for T1 and T2.

We know that there is no way to check whether there is a transfer constructor, let alone whether it is not thrown out. Therefore, before we get the new language features, boost :: has_nothrow_move is one of the remedy methods. It returns false for the user-defined type unless you have made it special. Therefore, when you compile a transfer constructor, you should make the trait special. For example, if we add a transfer constructor for STD: vector and STD: pair, we should also add:

namespace boost{    // All vectors have a (nothrow) move constructor    template <class T, class A>    struct has_nothrow_move<std::vector<T,A> > : true_type {};     // A pair has a (nothrow) move constructor iff both its    // members do as well.    template <class First, class Second>    struct has_nothrow_move<std::pair<First,Second> >      : mpl::and_<           boost::has_nothrow_move<First>         , boost::has_nothrow_move<Second>        > {};}

We admit that this is not nice. The C ++ Committee is still discussing details on how to solve the problem, but the following items have been accepted by the general committee:

  • We cannot destroy the existing code by quietly giving up the strong exception guarantee.
  • You can generate the default transfer constructor when appropriate -- just as Bjarne stroustrup
    In n2904, we recommend that you reduce this problem. This can fix pair and all similar types of problems, and increase the speed of some code by adding the generated transfer optimization and "free.
  • There are still some types that need to be processed manually.
"Problematic type"

Categorized as problematic types usually carry the sub-objects we want to transfer-security implementations are provided-and other sub-objects that we need "other operations. STD: vector is an example. It has a distributor, and its copy constructor may throw an exception:

vector(vector&& rhs)  : _alloc( std::move(rhs._alloc) )  , _begin( rhs._begin )  , _end( rhs._end )  , _cap( rhs._cap ){    // "something else"    rhs._begin = rhs._end = rhs._cap = 0;}

A simple member transfer, for example, in
The default generated in n2904 does not have the correct semantics here. In particular, it does not set _ begin, _ end, and _ cap of RHS to zero. However, if _ alloc does not have a non-throw transfer constructor, the data can only be copied in row 2nd. If this copy can throw an exception, the vector provides a writable transfer constructor.

For Language designers, the challenge is to avoid requiring users to provide the same information twice. You must specify in the signature of the transfer constructor that the member type can be transferred (rows 1 and 6 in the previous Pair Transfer constructor ), in the member initialization list, the Member is actually transferred (rows 10th and 11 ). One possibility being discussed is to use a new property syntax so that the transfer constructor to the vector can write like this:

vector(vector&& rhs) [[moves(_alloc)]]  : _begin( rhs._begin )  , _end( rhs._end )  , _cap( rhs._cap ){    rhs._begin = rhs._end = rhs._cap = 0;}

This structure will be
Sfinae drop, unless _ alloc itself has a non-throw transfer constructor, and this member will be transferred from the corresponding member of RHS, thus being implicitly initialized.

Unfortunately, there have been some differences on the role of attributes in C ++ 0x, so we do not know what syntax The Committee will accept, but at least we think that in principle we have understood what the problem is and how to solve it.

Follow-up

Thank you for reading this article. Today is the end. In the next article, we will discuss perfect forwarding, and we have not forgotten to owe you a survey on the transfer simulation of C ++ 03.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.