странные проблемы с конструктором C++/конструктором копирования в g++

#include <iostream>
using namespace std;

class X {
        public:
                X() {
                        cout<<"Cons"<<endl;
                }
                X(const X& x){
                        cout<<"Copy"<<endl;
                }
                void operator=(const X& x){
                        cout<<"Assignment called";
                }
};

X& fun() {
        X s;
        return s;
}

int main(){
        X s = fun();
        return 0;
}

Этот код также вызывает конструктор копирования. Почему это работает? Я помню, что когда я впервые запустил эту программу, она ошиблась. Но через некоторое время он начал называть эту копию минусами. и теперь работает!! странный.

Но если я заменю fun() следующим образом:

X fun() {
        X s;
        return s;
}

Затем скопируйте минусы. не называется. Я думал, что у копии минусы. будет вызван в этом случае. Но, как указал @flyfishr64, здесь в игру вступает RVO. Но это все еще не объясняет случай, когда я возвращаю ссылку. Я думаю, что это всегда должно segfault.

Любые объяснения?


person user855    schedule 27.11.2009    source источник
comment
Нельзя просто так сказать Но через какое-то время это копирование стало называться минусами. и теперь работает!! - Вы, должно быть, что-то изменили.   -  person Smashery    schedule 27.11.2009
comment
Неа. Единственное, что я сделал, это добавил 1 cout, а затем удалил его.   -  person user855    schedule 27.11.2009
comment
Вполне возможно. Потому что, когда я пробовал следующее, конструктор копирования не вызывался. X& fun() { X s; возврат с; } int main(){ X& x = fun(); вернуть 0; } Но с этой теорией не означает ли это, что возврат ссылки на локальную переменную всегда действителен? Потому что, если я делаю X s = fun() в основном, конструктор копирования все равно вызывается?   -  person user855    schedule 27.11.2009
comment
Нет, ссылка на локалку недействительна, но только если вы попытаетесь разыменовать ее. Просто вернуть это совершенно нормально. Более того, разыменование — это неопределенное поведение, и это не обязательно означает segfault, это означает именно это — неопределенное. Может случиться все, что угодно, и одним из особых случаев является объект, притворяющийся, что он все еще жив, что, скорее всего, произойдет, если вы действительно попытаетесь использовать возвращаемое значение (поскольку вы не перезаписали фрейм стека, на котором находится локальный был выделен еще).   -  person Pavel Minaev    schedule 27.11.2009


Ответы (4)


Чтобы расширить ответ @flyfishr64

Здесь вызывается конструктор копирования, потому что это:

X s = fun();

является инициализацией. Вы используете fun() для создания объекта, не вызывая конструктор по умолчанию. Это эквивалентно:

X s(fun());

«Минусы», которые вы видите распечатанными, относятся к экземпляру в fun(). Дополнительную информацию см. в этой статье: Оператор присваивания в C++.

person Doug T.    schedule 27.11.2009

Это возвращает ссылку на объект в стеке, который не существует после возврата метода - стек раскручивается, память все еще существует, но вы не должны ее использовать.

X& fun() {
        X s;
        return s;
}

Когда вы измените это на:

X fun() {
        X s;
        return s;
}

Теперь вы возвращаете копию. Если компилятор достаточно умен, он может сделать:

X fun() {
    return X();
}

В этом случае X размещается непосредственно в стеке вызывающих объектов, поэтому копирование не требуется.

Если это segfault или нет, зависит от того, обращаетесь ли вы к недопустимой памяти.

В вашем примере вы не получаете доступ к каким-либо значениям из структуры. Чтобы увидеть segfault, сначала сохраните ссылку, которую вы вернули с помощью fun(), добавьте некоторые переменные в структуру X, а после возврата из fun() вызовите другой метод, который внутренне выделяет часть памяти в стеке (это должно перезаписать исходную память, используемую X в fun) и сохраняет некоторые значения в стеке (предпочтительно 0). После возврата этого второго метода попробуйте распечатать значения из X, используя исходную ссылку, возвращенную из fun...

person stefanB    schedule 27.11.2009

В этом коде:

X fun() {
        X s;
        return s;
}

конструктор копирования не вызывается из-за оптимизации возвращаемого значения, которая позволяет компилятору обходить создание локальной переменной 's' и создавать X непосредственно в возвращаемой переменной.

Вы можете прочитать больше о RVO здесь

person Jeff Paquette    schedule 27.11.2009

Когда вы возвращаете ссылку на подобную локальную переменную, вы вызываете неопределенное поведение.

В данном случае это работает, потому что ни одна из функций class X на самом деле не использует указатель this, поэтому не имеет значения, что он больше недействителен.

person JoeG    schedule 28.01.2010
comment
Простой возврат такой ссылки вызывает UB? У вас есть ссылка на это? - person ; 28.01.2010