Подтвердить что ты не робот

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

В Eckel, Vol 1, pg: 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)? Что здесь происходит?

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

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

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

Другая часть ответа находится в: "Meyer: Effective С++", в "Введение":

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

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

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

Относительно моих вопросов:

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

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

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

Относительно:

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

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

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

Мейер говорит это (и Маттие): "будет создано временное связывание с этим Параметр ".

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

Относительно: что такое X(1)?

Meyer, Item27, Effective С++ - 3e, он говорит:

"Стили C-стиля выглядят следующим образом: (T) expression//выражение для выражения типа T

Присвоения в стиле функций используют этот синтаксис: T (выражение)//cast 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 в этот случай. "

Ответ 2

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

Единственное, что вы можете вызвать метод во временном режиме: так что 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) - это вызов метода, и методы могут быть вызваны во временном порядке:)

Ответ 3

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

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

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

Ответ 4

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

#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