Присвоение временному (например, 5 = 10, но для пользовательских типов) - PullRequest
2 голосов
/ 31 марта 2009

после выполнения некоторого тест-кода для этой ссылки:
Безопасно ли вызывать методы временного объекта?
Я обнаружил довольно странную особенность языка с ++, который не уверен, как он работает:

struct Test{
    int i;
    Test(int ii):i(ii){}
    Test& operator=(int ii){
        i = ii;
        return *this;
    }
    Test operator+(const Test& rhs){
        return Test(i + rhs.i);
    }
    void non_const_function(){
        i *= i;
    }
};

int main(){
        //code below gives errors, builtin types don't allow evil code
        //int i = 5+5 = 8;
        //int& iRef = 5+5;
        //int* iPtr = &(5+5);
        //5 = 10;

        Test x = Test(5) + Test(5) = 8;//assign to a temporary
        Test& xRef = Test(5) + Test(5);//reference to a temporary
        Test* xPtr = &(Test(5) + Test(5));//address of a temporary
        Test(5) = Test(10);//assign to a temporary
        Test(8).non_const_function();//call a non-const function
        return 0;
}

xRef и xPtr являются рабочими указателями с ожидаемыми значениями.
Конечно, я не написал бы такой код в реальном проекте, но мне все еще интересно, как / почему это работает.
Единственная информация, которую я нашел в Google по этому поводу, заключалась в том, что «если вы создаете ссылку на временный файл, время жизни временных файлов связывается с временем жизни ссылки»

Примечание:
не все компиляторы прощают, например, Metrowerks ARM (использует ли он GCC?) Допускает только постоянную ссылку на временные ссылки.
РЕДАКТИРОВАТЬ:
- увеличение предупреждения W4 в VC ++ 2008 показало много ошибок - это полезно знать.

РЕДАКТИРОВАТЬ 2:
Спасибо всем за помощь. Я вернулся к работе, исправляю сотни предупреждений.
ВЫВОД: используйте самое высокое предупреждение с самого начала (я даже обнаружил РЕАЛЬНУЮ ошибку благодаря / G4)

Ответы [ 6 ]

8 голосов
/ 31 марта 2009

Пойдем строка за строкой:

    Test x = Test(5) + Test(5) = 8;//assign to a temporary

Здесь нет ничего сложного. Временные объекты все еще являются обычными объектами, поэтому оператор присваивания работает с ними так же, как и с любым другим.

    Test& xRef = Test(5) + Test(5);//reference to a temporary

Как и Metroworks, мой GCC не допускает неконстантную ссылку на временный.

    Test* xPtr = &(Test(5) + Test(5));//address of a temporary

Кроме того, GCC предупреждает о взятии временного адреса по понятным причинам.

   Test(5) = Test(10);//assign to a temporary

Опять же, это просто задание, которое, как я объяснил выше, не имеет большого значения.

    Test(8).non_const_function();//call a non-const function

Временные объекты не являются постоянными. Ничто не мешает им вызывать неконстантные функции.

3 голосов
/ 31 марта 2009

Что касается первого случая, присваивания временному, я вспоминаю из моего Effective C ++, что рекомендуется объявить ваши операторы как

const Test operator+(const Test&) const

Это заставит объекты вести себя как встроенные типы в этом отношении, по крайней мере.

3 голосов
/ 31 марта 2009

Такие вещи встречаются повсеместно во многих объектно-ориентированных языках программирования, особенно с перегрузкой объектов.

Если первое утверждение было написано

Text x = Test(5).add(Test(5)).set(8);

где .add и .set возвращают левые тестовые экземпляры с ожидаемыми значениями, вы все еще были бы в замешательстве?

Чтобы разбить этот пример, вы создаете объект Test со значением 5, затем добавляете к нему другой объект Test со значением 5 и возвращаете первый объект Test, теперь со значением 10. Затем вы устанавливаете его значение к 8, и присвоение этого объекта x (ну, на самом деле это не присваивание, снова вы создаете объект Test с именем x, затем устанавливаете его значение на значение временного).

1 голос
/ 31 марта 2009

Мое прочтение вашего вопроса говорит о том, что вы путаетесь с временными затратами времени больше, чем что-либо еще. Я думаю, это поможет, если вы поставите пару cout s, как показано ниже:

struct Test{
    int i;
    Test(int ii):i(ii){ std::cout << "new object\n"; }
    Test& operator=(int ii){
        i = ii;
        return *this;
    }
    Test operator+(const Test& rhs){
        return Test(i + rhs.i);
    }
    void non_const_function(){
        i *= i;
    }
    ~Test() { std::cout << "deleting ...\n"; }
};

Теперь бегите, и вы сможете увидеть, что именно происходит под капотами. (Проверка стиля: не проверять самостоятельное назначение - не очень хорошая вещь.)

Это также поможет, если вы ссылаетесь на стандарт: он обсуждает что-то похожее на то, что вы опубликовали:

12.2 Временные объекты

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

5 Второй контекст - это когда ссылка связана с временным. Временный объект, к которому привязана ссылка, или временный объект, являющийся полным объектом подобъекта, к которому привязана ссылка, сохраняется в течение всего времени существования ссылки, за исключением случаев, указанных ниже. Временная привязка к элементу ссылки в конструкторе ctor-initializer (12.6.2) сохраняется до выхода из конструктора. Временная привязка к ссылочному параметру в вызове функции (5.2.2) сохраняется до завершения полного выражения, содержащего вызов. Временная привязка к возвращенному значению в операторе возврата функции (6.6.3) сохраняется до выхода из функции. Временная привязка к ссылке в новом инициализаторе (5.3.4) сохраняется до завершения полное выражение, содержащее новый инициализатор.

 struct S { int mi; const std::pair<int,int>& mp; };
 S a { 1, {2,3} };
 S* p = new S{ 1, {2,3} }; // Creates dangling reference

[Примечание: это может привести к появлению зависшей ссылки, и реализации рекомендуется выдайте предупреждение в таком случае. - конец примечания] Уничтожение временного, чье время жизни не продлевается путем привязки к ссылке, упорядочивается до уничтожения каждого временного, который построен ранее в том же полном выражении. Если время жизни двух или более временных объектов, к которым привязаны ссылки, заканчивается в одной и той же точке, эти временные объекты уничтожаются в этой точке в порядке, обратном завершению их построения. Кроме того, при уничтожении временных ссылок, связанных с ссылками, следует учитывать порядок уничтожения объектов со статическим, потоковым или автоматическим сроком хранения (3.7.1, 3.7.2, 3.7.3); то есть, если obj1 является объектом с той же продолжительностью хранения, что и временное хранилище, и созданным до создания временного, временное должно быть уничтожено до уничтожения obj1; если obj2 является объектом с такой же продолжительностью хранения, что и временное, и созданным после создания временного, временное должно быть уничтожено после уничтожения obj2. Пример:

 struct S {
    S();
    S(int);
    friend S operator+(const S&, const S&);
    ~S();
 };
 S obj1;
 const S& cr = S(16)+S(23);
 S obj2;

выражение C(16)+C(23) создает три временных. Первый временный T1 для хранения результата выражения C(16), второй временный T2 для хранения результата выражения C(23) и третий временный T3 для хранения результата сложения этих два выражения. Временный T3 затем привязывается к ссылке cr. Не указано, будет ли сначала создан T1 или T2. В реализации, где T1 создается до T2, гарантируется, что T2 будет уничтожено до T1. Временные значения T1 и T2 привязаны к опорным параметрам operator+; эти временные символы уничтожаются в конце полного выражения, содержащего вызов operator+. Временный T3, связанный с ссылкой cr, уничтожается в конце срока службы cr, тотесть, в конце программы. Кроме того, порядок уничтожения T3 учитывает порядок уничтожения других объектов со статической продолжительностью хранения. То есть, поскольку obj1 создается до T3, а T3 создается до obj2, гарантируется, что obj2 будет уничтожено до T3, а T3 уничтожено до obj1 .

1 голос
/ 31 марта 2009

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

0 голосов
/ 31 марта 2009

Взгляните на GotW # 4 , в котором рассматриваются механизмы создания пользовательского класса, который наиболее близко отражает встроенные арифметические типы. Если вы воспользуетесь его советом, ваша структура теста будет выглядеть примерно так:

struct Test{
    ...
    Test& operator+=( const Test& other ) {
        i += other.i;
        return *this;
    }
    ...
};

const Test operator+( const Test& lhs, const Test& rhs ) {
    Test ret( lhs );
    ret += rhs;
    return ret;
}

Теперь тест ведет себя намного лучше в ситуациях сложения (строки 1, 2 и 3 не компилируются). Это один из редких случаев, когда возвращение const-копии действительно полезно,

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...