Какой конструктор или оператор используется при возврате (C )

#c #return #copy-constructor #assignment-operator

#c #Возврат #копировать-конструктор #оператор присваивания

Вопрос:

Я запускаю этот код для экспериментов с конструктором копирования и оператором присваивания

 class AClass {

    private:
        int a;

    public:
        AClass (int a_) : a(a_) {  
            cout << " constructor AClass(int) " << a << endl;
        }

        AClass(const AClass amp; x) : a(x.a) { 
            cout << " copy constructor AClass(const AClass amp;) " << a << endl;
        }

        AClass amp; operator=(const AClass amp; x) { 
                a = x.a;
                cout << " AClassamp; operator=(const AClass amp;) " << a - endl;
                return *this;
        }
};

AClass g () {
    AClass x(8);
    return x;
}

int main () {

    cout << " before AClass b = g() " << endl;
    AClass b = g();
    cout << " after" << endl;

    cout << " before AClass c(g()) " << endl;
    AClass c  (g());
    cout << " after" << endl;
}
  

и обнаружил, что не отображается сообщение для return x;
Почему?
Не следует ли вызывать конструктор копирования или operator=?

Это результат:

 перед классом доступа b = g() 
 класс конструктора (int) 8 
 после

 перед классом c(g()) 
 класс конструктора (int) 8 
 после

Комментарии:

1. Вместо того, чтобы использовать <pre><code> в вашем вопросе, пожалуйста, выберите пример кода и нажмите {} кнопку в редакторе. Это позволяет лучше сохранять исходный код.

2. Поскольку вы экспериментируете, может быть полезно знать, что вы можете принудительно скопировать, если вы создадите x неавтоматическую переменную в g() , вот так: AClass *x = new AClass(8); return (*x); . Конечно, написание подобного кода позволит вам получить звание капитана S.S. MemoryLeak.

Ответ №1:

Компилятору разрешено исключить копирование в подобном случае. Это называется оптимизацией возвращаемого значения.

Комментарии:

1. Спасибо, но если RVO не выполнен, что выполняется: скопировать конструктор или operator=? Я сделал private operator=, и оба вызова все еще компилируются, поэтому напрашивается вывод, что будет использоваться конструктор копирования. Верно? Еще раз спасибо

2. @ciber: как правило, да, конструктор копирования используется для передачи параметров в функции и из них. Я не верю, что оператор присваивания может быть исключен таким образом, но не цитируйте меня по этому поводу.

3. @Dennis Нет, это не так. Операция присваивания никогда не вызывается компилятором, только явно программистом.

Ответ №2:

В C компилятору разрешено удалять вызовы конструктора копирования практически при любых обстоятельствах, даже если конструктор копирования имеет побочные эффекты, такие как распечатка сообщения. Как следствие, также разрешено вставлять вызовы конструктора копирования практически в любой момент, когда ему заблагорассудится. Это немного затрудняет написание программ для проверки вашего понимания копирования и присваивания, но означает, что компилятор может агрессивно удалять ненужное копирование в реальном коде.

Ответ №3:

Это известно как «оптимизация возвращаемого значения». Если объект возвращается по значению, компилятору разрешается сконструировать его в местоположении, доступном вызывающей стороне после возврата функции; в этом случае конструктор копирования вызываться не будет.

Также разрешено обрабатывать его как обычную автоматическую переменную и копировать его при возврате, поэтому конструктор копирования должен быть доступен. Вызывается он или нет, зависит от компилятора и настроек оптимизации, поэтому вам не следует полагаться ни на то, ни на другое поведение.

Ответ №4:

Это называется копированием. Компилятору разрешено удалять копии практически в любой ситуации. Наиболее распространенным случаем являются RVO и NRVO, что в основном приводит к построению возвращаемых значений на месте. Я продемонстрирую преобразование.

 void g (char* memory) {
    new (memory) AClass(8);
}

int main () {

    char __hidden__variable[sizeof(AClass)];
    g(__hidden__variable);
    AClassamp; b = *(AClass*)amp;__hidden__variable[0];
    cout -- " after" -- endl;

    // The same process occurs for c.
}
  

Код имеет тот же эффект, но теперь существует только один экземпляр AClass.

Ответ №5:

Возможно, компилятор оптимизировал вызов конструктора копирования. По сути, он перемещает объект.

Комментарии:

1. Учитывая изменения, внесенные в C 0x, я бы не решился на такое упрощение.

2. C 0x просто позволил четко указать движущиеся объекты. И даже тогда компилятор может полностью игнорировать вас и не вызывать ваш конструктор перемещения (что-то вроде того, что сбило меня с толку).

Ответ №6:

Если вы хотите увидеть, какой конструктор вызвал бы компилятор, вы должны победить RVO. Замените свою g() функцию таким образом:

 int i;
AClass g () {
    if(i) {
      AClass x(8);
      return x;
    } else {
      AClass x(9);
      return x;
    }
}
  

Комментарии:

1. Я тоже не уверен, что это сработает. Компилятору не требуется создавать обе возможные версии x в отдельных блоках памяти. Если бы у вас были две разные переменные в одной и той же области видимости, и вы условно возвращали бы любую из них, вам, вероятно, повезло бы больше.

2. На самом деле, это приводит к заметно отличающимся результатам в GCC без оптимизации.