1
votes

from N3337:

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

X does not have a user-declared copy constructor, X does not have a user-declared copy assignment operator, X does not have a user-declared move assignment operator, X does not have a user-declared destructor, and the move constructor would not be implicitly defined as deleted.

The first question is: why compiler generates move constructor when it shouldn't (there is user declared destructor and other functions which prevents generation of move constructor). Example program below prints constructorCounter=5 which means that there used move constructor (without move operations value::constructorCounter should be 10)

#include <iostream>

class value {
    public:
        value() {
            ++constructorCounter;
        }
        value(const int value)
            : _value(value)
        {
            ++constructorCounter;
        }
        value(const value& other)
            : _value(other._value)
        {
            ++constructorCounter;
        }
        const value& operator=(const value& rhs) {
            _value = rhs._value;
            return _value;
        }
        ~value() { }
        static int constructorCounter;
    private:
        int _value;
};

int value::constructorCounter = 0;

class array {
    public:
        //  array() = delete;
        //  array(array&&) = delete;
        array(const int size)
            : _size(size), _values(new value[size])
        {
            std::clog << "array(const int size)" << std::endl;
        }
        array(const array& rhs)
            : array(rhs._size)
        {
            std::clog << "array(const array& rhs)" << std::endl;
            for (int i = 0; i < _size; ++i)
                _values[i] = rhs._values[i];
        }
        array& operator=(const array&) {
            std::clog << "array& operator=(const array&)" << std::endl;
        }
        ~array() {
            delete [] _values;
        }
    private:
        value* _values;
        int _size;
};

int main(int argc, char *argv[]) {
    array c(array(5));
    std::clog << "constructor counter=" << value::constructorCounter << std::endl;

    return 0;
}

Second related question: why program fail to compile if i disable move constructor array(array&&) = deleted;, why there is no 'fall back' to copy consturctor array(const array&) ?

2
array c(array(5)); array(5) is an rvalue, therefore the move constructor will be called.babu646

2 Answers

10
votes

At first, the line:

array c(array(5));

Leads to the copy constructor being used (because there is no user declared move constructor). However since it happens to be the c object construction, the compiler is able to elide the copy and construct your array object in-place.

Now when you user-declare the move constructor as delete-d, it is considered during overload resolution and indeed while resolving the call in line:

array c(array(5));

array(array&&) will be a perfect match... But it is deleted so overload resolution stops (perfect match), and an error is output (the function is deleted).

As suggested in comments, would you have been using the C++17 standard for your compilation, the line

array c(array(5));

Would have resulted in a guaranteed copy elision since it fits the following scenario:

In initialization, if the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object

Rendering your code strictly equivalent to:

array c(5);
3
votes

Move constructors are not generated here. Pedantically, the move constructor and assignment are not declared in this case.

What you observe is the compiler elides copying in array c(array(5));. See copy elision for more details:

In initialization, if the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object:

T x = T(T(T())); // only one call to default constructor of T, to initialize x

When you do array(array&&) = deleted; the above expression considers the move constructor now (it was previously not declared) and fails because it is deleted.