Временные объекты - когда они создаются, как вы узнаете их в коде?

В Экель, том 1, стр. 367

//: C08:ConstReturnValues.cpp
// Constant return by value
// Result cannot be used as an lvalue
class X {
   int i;
public:
   X(int ii = 0);
   void modify();
};

X::X(int ii) { i = ii; }

void X::modify() { i++; }

X f5() {
   return X();
}

const X f6() {
   return X();
}

void f7(X& x) { // Pass by non-const reference
   x.modify();
}

int main() {
   f5() = X(1); // OK -- non-const return value
   f5().modify(); // OK
// Causes compile-time errors:
//! f7(f5());
//! f6() = X(1);
//! f6().modify();
//! f7(f6());
} ///:~

Почему f5() = X(1) succed? Что здесь происходит???

Q1. Когда он делает X(1) - что происходит здесь? Это вызов конструктора - не стоит ли тогда читать X::X(1); Это экземпляр класса - не экземпляр класса что-то вроде: X a(1); Как компилятор определяет, что X(1) является?? Я имею в виду.. название украшения происходит так.. X(1) вызов конструктора будет выглядеть примерно так: globalScope_X_int как имя функции..???

Q2. Конечно, временный объект используется для хранения результирующего объекта, который X(1)создает, а затем не будет назначено объекту f5() возвращает (который также будет временным объектом)? При условии f5() возвращает временный объект, который вскоре будет отброшен, как он может назначить одно постоянное временное значение другому постоянному временному??? Может ли кто-нибудь объяснить, почему:f7(f5()); должен приводить к постоянному временному, а не старому f5();

4 ответа

Решение

Я не был полностью удовлетворен ответами, поэтому я взглянул на:

"Более эффективный C++", Скотт Мейерс. Пункт 19: "Понять происхождение временных объектов"

, Что касается освещения Брюсом Экелом "Временных", ну, как я подозреваю и как прямо указывает Кристиан Рау, это совершенно неправильно! Хмм! Он (Экель) использует нас как морских свинок!! (это будет хорошая книга для новичков, как я, когда он исправит все свои ошибки)

Мейер: "Истинные временные объекты в C++ невидимы - они не появляются в вашем исходном коде. Они возникают всякий раз, когда создается объект без кучи, но не именуется. Такие безымянные объекты обычно возникают в одной из двух ситуаций: когда неявные преобразования типов применяются для успешного вызова функций и когда функции возвращают объекты. "

"Сначала рассмотрим случай, когда временные объекты создаются для успешного вызова функции. Это происходит, когда тип объекта, передаваемого в функцию, не совпадает с типом параметра, с которым она связана".

"Эти преобразования происходят только при передаче объектов по значению или при передаче в параметр reference-to-const. Они не происходят при передаче объекта в параметр reference-to-non-const".

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

"Каждый раз, когда вы видите параметр reference-to-const, существует вероятность, что будет создан временный объект для привязки к этому параметру. Каждый раз, когда вы видите функцию, возвращающую объект, будет создан временный объект (и позже уничтожен)".

Другая часть ответа находится в: "Мейер: эффективный C++", во "Введение":

msgstr "конструктор копирования используется для инициализации объекта другим объектом того же типа:"

String s1;       // call default constructor
String s2(s1);   // call copy constructor
String s3 = s2;  // call copy constructor

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

По поводу моих вопросов:

f5() = X(1) //what is happening?

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

Временные объекты создаются потому, что согласно Мейеру (верхние абзацы) обе функции возвращают значения, поэтому создаются временные объекты. Как указал Матье, используя псевдокод, он становится:__0.operator=(__1) и происходит побитовое копирование (выполняется компилятором).

Что касается:

void f7(X& x);
f7(f5);

следовательно, временное не может быть создано (Мейер: верхние абзацы). Если это было объявлено: void f7(const X& x); тогда временный был бы создан.

Относительно временного объекта, являющегося константой:

Мейер говорит это (и Матье): "Будет создан временный объект для привязки к этому параметру".

Таким образом, временный объект связан только с константной ссылкой и сам по себе не является "константным" объектом.

Что касается: что такое X(1)?

Мейер, Item27, Effective C++ - 3e, он говорит:

"Приведения в стиле C выглядят так: (T) выражение // приведение выражения к типу T

Приведения в стиле функции используют следующий синтаксис: T(expression) // приведение выражения к типу T"

Так X(1) это приведение в стиле функции. 1 выражение приводится к типу X,

И Мейер говорит это снова:

"Примерно единственный раз, когда я использую приведение в старом стиле, это когда я хочу вызвать явный конструктор для передачи объекта в функцию. Например:

class Widget {
  public:
    explicit Widget(int size);
    ...
};

void doSomeWork(const Widget& w);
doSomeWork(Widget(15)); //create Widget from int
                        //with function-style cast

doSomeWork(static_cast<Widget>(15));

Так или иначе, преднамеренное создание объекта не "ощущается" как приведение, поэтому в этом случае я бы, вероятно, использовал приведение в стиле функции вместо static_cast ".

Все ваши вопросы сводятся к правилу в C++, которое гласит, что временный объект (тот, у которого нет имени) не может быть связан с неконстантной ссылкой. (Потому что Страуструп чувствовал, что это может вызвать логические ошибки...)

Одна загвоздка в том, что вы можете вызывать метод временно: X(1).modify() хорошо но f7(X(1)) не является.

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

Поэтому следующее утверждение X(1).modify(); можно полностью перевести на:

{
    X __0(1);
    __0.modify();
} // automatic cleanup of __0

Имея это в виду, мы можем атаковать f5() = X(1);, У нас здесь два временных человека и задание. Оба аргумента присваивания должны быть полностью оценены до вызова присваивания, но порядок не является точным. Один из возможных переводов:

{
    X __0(f5());
    X __1(1);
    __0.operator=(__1);
}

(другой перевод меняет порядок, в котором __0 а также __1 инициализируются)

И ключом к этому является то, что __0.operator=(__1) является вызовом метода, и методы могут быть вызваны для временных файлов:)

  1. Это действительно вызов конструктора, выражение, оценивающее временный объект типа X, Выражения формы X([...]) с X имя типа является вызовом конструктора, который создает временные объекты типа X (хотя я не знаю, как объяснить это в надлежащем стандарте, и есть особые случаи, когда анализатор может вести себя по-другому). Это та же самая конструкция, которую вы используете в своем f5 а также f6 функции, просто опуская необязательные ii аргумент.

  2. Временный созданный X(1) живет (не уничтожается / недействителен) до конца полного выражения, содержащего его, что обычно означает (как в этом случае с выражением присваивания) до точки с запятой. Аналогично делает f5 создать временный X и вернуть его на сайт вызова (внутри main), таким образом, копируя его. Так что в основном f5 вызов также возвращает временный X, Это временное X Затем назначается временный X создано X(1), После того, как это будет сделано (и точка с запятой достигнута, если хотите), оба временные уничтожаются. Это назначение работает, потому что эти функции возвращают обычные неконстантные объекты, независимо от того, являются ли они просто временными и уничтожаются после того, как выражение полностью оценено (таким образом, делая назначение более или менее бессмысленным, даже если оно полностью допустимо).

    Не работает с f6 так как это возвращает const X на который вы не можете назначить. Аналогично делает f7(f5()) не работает, так как f5 создает временные и временные объекты, не привязанные к неконстантным ссылкам lvalue X& (C++11 ввел rvalue ссылки X&& для этого, но это другая история). Это будет работать, если f7 взял постоянную ссылку const X&, поскольку константы lvalue привязываются к временным файлам (но затем f7 само по себе больше не будет работать, конечно).

Вот пример того, что на самом деле происходит, когда вы выполняете свой код. Я сделал некоторые изменения, чтобы прояснить процессы за сценой:

#include <iostream>

struct Object
{
    Object( int x = 0 ) {std::cout << this << ": " << __PRETTY_FUNCTION__ << std::endl;}
    ~Object() {std::cout << this << ": " << __PRETTY_FUNCTION__ << std::endl;}
    Object( const Object& rhs ){std::cout << this << ": " << __PRETTY_FUNCTION__ << " rhs = " << &rhs << std::endl;}
    Object& operator=( const Object& rhs )
    {
        std::cout << this << ": " << __PRETTY_FUNCTION__ << " rhs = " << &rhs << std::endl;
        return *this;
    }
    static Object getObject()
    {
        return Object();
    }
};

void TestTemporary()
{
    // Output on my machine
    //0x22fe0e: Object::Object(int) -> The Object from the right side of = is created Object();
    //0x22fdbf: Object::Object(int) -> In getObject method the Temporary Unnamed object is created
    //0x22fe0f: Object::Object(const Object&) rhs = 0x22fdbf -> Temporary is copy-constructed from the previous line object
    //0x22fdbf: Object::~Object() -> Temporary Unnamed is no longer needed and it is destroyed
    //0x22fe0f: Object& Object::operator=(const Object&) rhs = 0x22fe0e -> assignment operator of the returned object from getObject is called to assigne the right object
    //0x22fe0f: Object::~Object() - The return object from getObject is destroyed
    //0x22fe0e: Object::~Object() -> The Object from the right side of = is destroyed Object();

    Object::getObject() = Object();
}

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

-fno-elide-constructors
Другие вопросы по тегам