GMP and C++11 move constructors

Hans Åberg haberg-1 at telia.com
Sun May 20 12:57:17 UTC 2018


> On 20 May 2018, at 13:37, Marco Bodrato <bodrato at mail.dm.unipi.it> wrote:
> 
> Il Ven, 18 Maggio 2018 3:17 pm, Hans Åberg ha scritto:
>>> On 18 May 2018, at 15:01, Marc Glisse <marc.glisse at inria.fr> wrote:
>>> On Fri, 18 May 2018, Hans Åberg wrote:
>>> 
>>>> FYI, if one follows the GMP API, it forces C++11 move constructors to
>>>> make a heap allocation via mpz_init, which then is immediately
> 
>>> If you look at the current repository, you'll notice that mpz_init does
>>> not perform a heap allocation anymore.
>> 
>> But it looks like it isn't NULL, so I gather mpz_clear checks for that
>> value, otherwise it will crash on 'free'.
> 
> Yes, you need to init an mpz, before clear-ing it. But, as Marc
> underlined, this will not imply a heap allocation, with the current
> development code.

It is just curious, because there is a value that guarantees deallocators to do nothing, and that is (void*)0.

But I rewrote my code to use mpz_init (see below).

> I'm not an expert about C++, at all. Could you please write here how the
> GMP_API forces to write the constructors you are speaking about, and how
> you would like to write them instead?

Before C++11, there were only copy constructors and assignment operators, which make new allocation:
  class integer {
    mpz_t value_;

  public:
    integer() { mpz_init(value_); }
    ~integer() { mpz_clear(value_); }

    // Copy constructor:
    integer(const integer& x) { mpz_init(value_); mpz_set(value_, x.value_); }

    integer& operator=(const integer& x) {  // Assignment operator.
      // Do nothing if assigning to self:
      if (this != &x)  mpz_set(value_, x.value_);
      return *this;
    }
    ...
  };

A function stack typically works by creating temporary objects, called r-values, by forwarding them to the next call. But this is is expensive. So C++11 introduced move constructors and assignments, written with &&, that when applied to the temporaries move the heap pointer if implemented so.

The moved from object, or r-value, then must be in a legal state before the destructor is applied. As it is a allocated heap pointer, the simplest way to set it to NULL, ensuring that the when the destructor sees it and applies the deallocator, nothing happens.

Now return to GMP, adding the move operators. We get:
  
  inline void mpz_move(mpz_t x, mpz_t y) { *x = *y; }

  class integer {
  public:
    mpz_t value_;

  public:
    integer() { mpz_init(value_); }
    ~integer() { mpz_clear(value_); }

    // Copy and move constructor:
    integer(const integer& x) { mpz_init(value_); mpz_set(value_, x.value_); }
    integer(integer&& x) { mpz_move(value_, x.value_); mpz_init(x.value_); }

    integer& operator=(const integer& x) {  // Assignment operator.
      // Do nothing if assigning to self:
      if (this != &x)  mpz_set(value_, x.value_);
      return *this;
    }

    integer& operator=(integer&& x) {  // Move assignment operator.
      // Do nothing if assigning to self:
      if (this != &x) { mpz_clear(value_); mpz_move(value_, x.value_); mpz_init(x.value_); }
      return *this;
    }
    ...
  };

The only reason is to use mpz_move is to create an API: If GMP changes the underlying mpz_t, this code might break.

Now, instead of
  integer(integer&& x) { mpz_move(value_, x.value_); mpz_init(x.value_); }
one might try
  integer(integer&& x) { mpz_move(value_, x.value_); mpz_null(x.value_); }
using
  inline void mpz_null(mpz_t x) {
    x[0]._mp_alloc = 0;
    x[0]._mp_size = 0;
    x[0]._mp_d = NULL;    // Setting allocation pointer to null.
  }

This works in the current implementation, as it just applies 'free', I think. 

But in the new implementation, it will break, since you use another value than NULL.

So, returning to your question above, as the GMP API does not explicitly specify that NULL is allowed, one cannot reliably assume that it will work, and must use mpz_init. And, in fact, it will break, when changing it to a non-NULL value.

Also, the GMP supplied C++ headers use a swap in the move assignment operator, but that is bad for various reason, cf. [1-2].

1. https://stackoverflow.com/questions/6687388/why-do-some-people-use-swap-for-move-assignments
2. http://scottmeyers.blogspot.com/2014/06/the-drawbacks-of-implementing-move.html?m=1




More information about the gmp-discuss mailing list