Я не могу понять это поведение конструктора копирования

У меня странное поведение со следующим:

using namespace std;
struct Number
{
    Number(int init) : a(init) {}
    Number() {};
    int a;
    static Number& getNumber() { return Number(555); }

    //Number(const Number& other)
    //{
    //  a = other.a;
    //}  // I've commented this out you'll see why
};

int main()
{
    Number num1;  // Is now junk
    Number num2;  // Is now junk
    num2 = Number::getNumber(); // num 2 is still junk

    Number num3 = Number::getNumber(); // num3 has been assigned 555. 
                                       // If I define my own copy constructor
                                       // (uncomment it), it stays junk.
    cout << num3.a;
}

Когда я создаю свой собственный конструктор копирования, принимает ли он const или нет, значение, входящее в аргумент "other", является ненужным. Я не получаю такого поведения, если конструктор копирования по умолчанию. Я попробовал это на IDEOne, используя GCC, и этот код не компилируется. Однако в моей Visual Studio он работает так, как я описал.

Мне очень трудно понять правила того, как долго временный еще действует. Например, я подумал, что если getNumber() возвращает ссылку на локальный временный объект, то все в порядке, если он назначен непосредственно в той же строке. Я был неправ.

3 ответа

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

static Number getNumber() { return {555}; }

И теперь возвращаемое вами число напрямую строится из возвращаемого значения.

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

Я узнал кое-что, пытаясь ответить на это.

Каковы возможные способы возврата объекта из статической функции?

По значению

Обычно это правильный путь. Часто компилятор будет использовать Return-Value-Optimization или иным образом избегать фактического копирования объекта несколько раз. Если вы не уверены, что это, вероятно, то, что вы хотите.

Если вам не достаточно используемого по умолчанию конструктора копирования, убедитесь, что вы определили свой собственный, не забывая определить его для аргумента const ref. Если вы используете C++11, определение отдельного конструктора перемещения может быть полезным.

По неконстантной ссылке

Это неверно, потому что это ссылка (фактически указатель) на область памяти, которая раньше содержала переменную, которая больше не существует.

Это ошибка в GCC. Раньше это было разрешено в Visual Studio, хотя я слышал, что это не может быть больше. Вы можете скомпилировать с опцией компилятора /Za, чтобы отключить различные специфичные для Microsoft расширения, если хотите.

По константной ссылке

Это не ошибка, но предупреждение и неопределенное поведение.

Вы можете привязать const ref к временному объекту. См. Статью Херба Саттера: https://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/

например, "const Number& num = get_number_by_value()" часто возвращает копию временного объекта и удаляет копию, а затем временный объект будет привязан к ссылке, и его время жизни будет увеличено таким образом, который работает специально для const ref (но не другие ссылки или указатели).

Тем не менее, я только что узнал, что теперь при поиске выясняю, что это технически применимо к возвращению временного значения из функции, но это время жизни не увеличивается, если оно затем присваивается другому const ref.

Итак, ваше дело

Number num = get_number_by_const_ref()

может работать нормально, но

const Number& num = get_number_by_const_ref()

может нет.

Вернуть постоянную ссылку на статическую переменную-член

Это обычно не полезно, но если ваш класс очень дорог в создании (требует много вычислений или использует ГБ памяти), и вы хотите возвращать конкретный экземпляр несколько раз, у вас может быть частная переменная-член const static: класс, в котором хранится экземпляр, который вы можете вернуть по ссылке.

Помните, что если у вас есть статическая переменная-член, содержащая переменную экземпляра класса, ее нужно инициализировать вне класса в файле.c, чтобы была доступна функция конструктора.

Функция static Number& getNumber() { return Number(555); } создает временный Number и возвращает ссылку на него. Временный объект перестает существовать в конце функции, то есть возвращаемая вами ссылка теперь ссылается на уничтоженный объект. Это неопределенное поведение, что означает, что поведение может быть любым, включая иногда работать. Компилятор не обязан диагностировать эту ошибку, но некоторые (такие как GCC) делают. Если вы намереваетесь вернуть изменяемую ссылку на общий экземпляр, объявите статический локальный объект в теле функции и верните ссылку на него.

static Number& getNumber() 
{ 
    static Number my_instance{555}; 
    return my_instance;
}
Другие вопросы по тегам