Почему конструктор копирования не вызывается в этом коде

Итак, почему конструктор копирования не вызывается в функции «const Integer operator+(const Integer &rv)». Это из-за РВО. Если да, что мне нужно сделать, чтобы предотвратить это?

#include <iostream>

using namespace std;

class Integer {
    int i;

public:
    Integer(int ii = 0) : i(ii) {
        cout << "Integer()" << endl;
    }

    Integer(const Integer &I) {
        cout << "Integer(const Integer &)" << endl;
    }

    ~Integer() {
        cout << "~Integer()" << endl;
    }

    const Integer operator+(const Integer &rv) const {
        cout << "operator+" << endl;
        Integer I(i + rv.i);
        I.print();
        return I;
    }

    Integer &operator+=(const Integer &rv) {
        cout << "operator+=" << endl;
        i + rv.i;
        return *this;
    }

    void print() {
        cout << "i: " << i << endl;
    }
};

int main() {
    cout << "built-in tpes:" << endl;
    int i = 1, j = 2, k = 3;
    k += i + j;
    cout << "user-defined types:" << endl;
    Integer ii(1), jj(2), kk(3);
    kk += ii + jj;

}

Я получаю сообщение об ошибке, если я закомментирую конструктор копирования. Я ожидаю, что конструктор копирования будет вызван, когда оператор + вернется. Ниже приведен вывод программы

built-in tpes:
user-defined types:
Integer()
Integer()
Integer()
operator+
Integer()
i: 3 // EXPECTING Copy Constructor to be called after this
operator+=
~Integer()
~Integer()
~Integer()
~Integer()

person nik    schedule 15.10.2011    source источник
comment
Где вы ожидали, что ваш конструктор копирования будет вызываться?   -  person André Puel    schedule 15.10.2011
comment
Показ того, что распечатано, даст нам больше информации о реальной ситуации, с которой вы столкнулись. И, кстати, ваш конструктор копирования ничего не копирует. Или конструктор копирования I::i вызывается автоматически? Я так не думаю, но не совсем уверен.   -  person Christian Rau    schedule 15.10.2011
comment
@AndréPuel: Он ожидает, что копировщик будет вызван, когда operator+ вернется!   -  person Nawaz    schedule 15.10.2011
comment
И ваш operator += неверен, в средней строке, вероятно, должно быть написано i += rv.i;   -  person Frunsi    schedule 15.10.2011
comment
Если вам нужно отключить RVO, скорее всего, вы делаете что-то не так.   -  person Alan    schedule 15.10.2011
comment
Почему вы хотите отключить RVO?   -  person David Rodríguez - dribeas    schedule 16.10.2011


Ответы (2)


Это из-за РВО. Если да, что мне нужно сделать, чтобы предотвратить это?

да. Но он не был вызван из-за оптимизации возвращаемого значения компилятором.

Если вы используете GCC, используйте опцию -fno-elide-constructors, чтобы избежать этого.

GCC 4.6.1 руководство говорит,

-fno-elide-конструкторы

Стандарт C++ позволяет реализации не создавать временный объект, который используется только для инициализации другого объекта того же типа. Указание этой опции отключает эту оптимизацию и заставляет G++ вызывать конструктор копирования во всех случаях.

person Nawaz    schedule 15.10.2011
comment
Это сработало. Но есть ли другой способ сделать это с помощью параметров компилятора. Я вызвал print для локального объекта внутри operator+, думая, что это предотвратит RVO. - person nik; 15.10.2011
comment
@NikhilRathod: Что ты имеешь в виду? Что сработало? Все, что я говорю, это параметры компилятора, о чем еще вы говорите? - person Nawaz; 15.10.2011
comment
@NikhilRathod: Да. Теперь какой у вас следующий вопрос? У тебя вроде есть? - person Nawaz; 15.10.2011
comment
Что мне нужно сделать с локальным объектом I в операторе+, чтобы компилятор не делал RVO? - person nik; 15.10.2011
comment
@NikhilRathod: если вы не хотите использовать какой-либо параметр компилятора для предотвращения RVO, а вместо этого хотите что-то сделать с локальным объектом, чтобы избежать RVO, то, насколько мне известно, вы ничего не можете сделать с локальным объектом , для предотвращения РВО. - person Nawaz; 15.10.2011
comment
@NikhilRathod: вам следует вообще избегать побочных эффектов (например, печатать что-то, регистрировать «я» где-то и т. д.) в конструкторах копирования. Даже избегайте их в обычных конструкторах. Это приведет к плохому дизайну. - person Frunsi; 17.10.2011

(N)RVO — одна из самых простых в реализации оптимизаций. В большинстве соглашений о вызовах для возврата по значению вызывающая сторона резервирует место для возвращаемого объекта, а затем передает функции скрытый указатель. Затем функция конструирует объект по заданному адресу. То есть kk += ii + jj; переводится примерно так:

Integer __tmp;

//                  __rtn  this  arg
Integer::operator+( &tmp,  &ii,  jj );

kk += __tmp;

Функция (в данном случае Integer::operator+ принимает первый скрытый аргумент __rtn, который является указателем на неинициализированный блок памяти размером sizeof(Integer) байт, где должен быть создан объект, второй скрытый аргумент this, а затем аргумент функции в код.

Затем реализация функции транслируется в:

Integer::operator+( Integer* __rtn, Integer const * this, const Integer &rv) {
    cout << "operator+" << endl;
    new (__rtn) Integer(i + rv.i);
    __rtn->print();
}

Поскольку соглашение о вызовах передает указатель, функции не нужно резервировать дополнительное пространство для локального целого числа, которое затем будет скопировано, поскольку она может просто встроить I в ваш код прямо в полученный указатель и избежать копирования.

Обратите внимание, что не во всех случаях компилятор может выполнять NRVO, в частности, если у вас есть два локальных объекта в функции, и вы возвращаете любой из них в зависимости от условия, которое невозможно вывести из кода (скажем, значение аргумента функции ). Хотя вы могли бы сделать это, чтобы избежать RVO, дело в том, что это сделает ваш код более сложным, менее эффективным и трудным в обслуживании.

person David Rodríguez - dribeas    schedule 15.10.2011