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

Каков жизненный цикл объекта С++?

Я опытный разработчик C, который только что попал на С++, и я должен признать, я очень смущен тем, как много способов создавать, сохранять и уничтожать объекты С++. В C жизнь проста: назначение с = копиями в стеке и malloc/free управление данными в куче. С++ далек от этого, или так мне кажется.

В свете этого, вот мои вопросы:

  • Каковы все способы создания объекта С++? Direct/copy конструктор, назначение и т.д. Как они работают?
  • Каковы все различные синтаксисы инициализации, связанные со всеми этими типами создания объектов? Какая разница между T f = x, T f(x);, T f{x}; и т.д.?
  • Самое главное, когда правильно копировать/назначать/все = на С++ и когда вы хотите использовать указатели? В C я очень сильно использовал множество указателей, потому что назначение указателя дешево, но копирование структуры меньше. Как это влияет на семантику копирования С++?
  • Наконец, что это такое, как shared_ptr, weak_ptr и т.д.?

Прошу прощения, если это несколько широкий вопрос, но я очень смущен, когда использовать что (даже не упоминая о моем замешательстве в управлении памятью в коллекциях и операторе new), и я чувствую, что все Я знал, что управление памятью C распадается на С++. Это правда, или моя ментальная модель просто неверна?

Подводя итог: как создаются, инициализируются и уничтожаются объекты С++ и когда я должен использовать каждый метод?

4b9b3361

Ответ 1

Прежде всего, ваши навыки управления памятью полезны в С++, просто они находятся на уровне ниже С++, но они там...

О ваших вопросах они немного шире, поэтому я постараюсь сделать это коротко:

1) Каковы все способы создания объекта С++?

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

Назначение не создает новый объект, он просто копирует из одного объекта в другой (думает memcpy, но умнее).

2) Каковы все различные синтаксисы инициализации, связанные со всеми этими типами создания объектов? Какая разница между T f = x, T f (x);, T f {x};, и т.д.?

  • T f(x) является классическим способом, он просто создает объект типа T, используя конструктор, который принимает в качестве аргумента x.
  • T f{x} - это новый синтаксис С++ 11, так как он может использоваться для инициализации агрегатных типов (массивов и т.д.), но не эквивалентен первому.
  • T f = x зависит от того, имеет ли тип x тип T. Если это так, то оно эквивалентно первому, но если оно имеет другой тип, то оно эквивалентно T f = T(x). Не то, чтобы это действительно имело значение, потому что компилятору разрешено оптимизировать дополнительную копию (копия elision).
  • T(x). Ты забыл об этом. Создается временный объект типа T (с использованием того же конструктора, что и выше), он используется везде, где это происходит в коде, а в конце текущего полного выражения он уничтожается.
  • T f. Это создает значение типа T, используя конструктор по умолчанию, если он доступен. Это просто конструктор, который не принимает никаких параметров.
  • T f{}. По умолчанию выполнено, но с новым унифицированным синтаксисом. Обратите внимание, что T f() не является объектом типа T, а вместо него возвращает функцию T!.
  • T(). Временный объект, использующий конструктор по умолчанию.

3) Самое главное, когда правильно скопировать/назначить/whatever = в С++ и когда вы хотите использовать указатели?

Вы можете использовать то же, что и в C. Подумайте о копировании/присваивании, как если бы он был memcpy. Вы также можете передавать ссылки, но вы также можете подождать некоторое время, пока не почувствуете себя комфортно с ними. Вы должны сделать следующее: не используйте указатели в качестве вспомогательных локальных переменных, используйте вместо них ссылки.

4) Наконец, что это такое, как shared_ptr, weak_ptr и т.д.?

Это инструменты на вашем инструменте С++. Вам придется учиться на опыте и некоторых ошибках...

  • shared_ptr используется при совместном владении объектом.
  • unique_ptr использовать, когда право собственности на объект уникально и недвусмысленно.
  • weak_ptr используется для разрыва петель в деревьях shared_ptr. Они не обнаруживаются автоматически.
  • vector. Не забывайте об этом! Используйте его для создания динамических массивов.

PS: Вы забыли спросить о деструкторах. IMO, деструкторы - это то, что дает С++ его индивидуальность, поэтому обязательно используйте их много!

Ответ 2

Это довольно широкий вопрос, но я дам вам отправную точку.

То, что известно в C как "переменная стека", также называется объектом с "автоматическим хранилищем". Жизнеспособность объекта с автоматическим хранилищем довольно легко понять: он создается, когда элемент управления достигает определенной в нем точки, а затем уничтожается, когда он выходит из области видимости:

int main() {
  int foo = 5; // creation of automatic storage
  do_stuff();
  foo = 1;

  // end of function; foo is destroyed.
}

Теперь следует отметить, что = 5 считается частью синтаксиса инициализации, а = 1 считается операцией присваивания. Я не хочу, чтобы вы путались при использовании = для двух разных слов в грамматике языка.

Во всяком случае, С++ берет автоматическое хранилище немного дальше и позволяет запускать произвольный код во время создания и уничтожения этого объекта: конструкторы и деструкторы. Это порождает замечательную идиому под названием RAII, которую вы должны использовать, когда это возможно. С RAII управление ресурсами становится автоматическим.

что это такое, как shared_ptr, weak_ptr и т.д.?

Хорошие примеры RAII. Они позволяют обрабатывать динамический ресурс (malloc/free calls) в качестве объекта автоматического хранения!

Самое главное, когда правильно копировать/назначать/whatever = на С++ и когда вы хотите использовать указатели? В C я очень сильно использовал множество указателей, потому что назначение указателя дешево, но копирование структуры меньше. Как это влияет на семантику копирования С++?

const ссылки везде, особенно для параметров функции. const refs избегает копирования и предотвращает модификацию объекта. Если вы не можете использовать const ref, возможно, нормальная ссылка подходит. Если по какой-либо причине вы хотите использовать reset ссылку или установить ее в значение null, используйте указатель.

Каковы все способы создания объекта С++? Direct/copy конструктор, назначение и т.д. Как они работают?

Короче говоря, все конструкторы создают объекты. Назначение не выполняется. Прочтите книгу для этого.

Ответ 3

  • Существует много способов создания неявных объектов на С++, кроме явных. Почти все они используют copy-constructor класса объектов. Помните:. Неявное копирование может потребовать, чтобы конструктор копирования и/или оператор присваивания типа T был объявлен в области public в зависимости от того, где происходит копирование.
    Так в курсе:

    а) явное создание совершенно нового объекта в стеке:

    T object(arg);

b) явное копирование существующего объекта:

T original(arg);
...
T copy(original);

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

T *ptr = new T(arg);

d) неявное создание совершенно нового объекта, конструктор которого принимает только один параметр и не имеет модификатора explicit, например:

class T
{
public:
    T(int x) : i(x) {}
private:
    int i;
}
...
T object = 5; // actually implicit invocation of constructor occurs here

e) неявное копирование объекта, переданного функции по значению:

void func(T input)
{
    // here `input` is a copy of an object actually passed
}
...

int main()
{
    T object(arg);
    func(object); // copy constructor of T class is invoked before the `func` is called
}

f) неявное копирование обработки объекта исключения по значению:

void function()
{
    ...
    throw T(arg); // suppose that exception is always raised in the `function`
    ...
}
...
int main()
{
    ...
    try {
        function();
    } catch (T exception) { // copy constructor of T class is invoked here
        // handling `exception`
    }
    ...
}

g) Создание нового объекта с использованием оператора присваивания. Я не использовал слово "copy", потому что в этом случае имеет место реализация оператора присваивания определенного типа. Если этот оператор не реализован, реализация по умолчанию создается компилятором, так как он имеет то же поведение, что и конструктор копирования по умолчанию.

class T
{
    T(int x) : i(x) {}
    T operator=() const
    {
        return T(*this); // in this implementation we explicitly call default copy constructor
    }
}
...
int main()
{
   ...
   T first(5);
   T second = first; // assingment operator is invoked
   ...
}

Хорошо, это то, что я помню, не глядя в книгу Страуструпа. Может быть, что-то пропущено.
Пока я писал это, был принят какой-то ответ, поэтому я останавливаюсь на этом этапе. Могут быть полезны детали, которые я перечисляю.