C ++: поведение векторного распределителя, выделение памяти и умные указатели - PullRequest
0 голосов
/ 04 октября 2018

Обратитесь к следующему фрагменту кода.

Согласно моему пониманию:

a) 'p1' и 'p2' объекты создаются в стеке и получаютуничтожается в конце метода getPoints ().

b) Когда p1 и p2 добавляются к вектору с помощью push_back ()Распределитель по умолчанию создает новые экземпляры Point и копирует значения (x, y) p1 и p2 в эти вновь созданные экземпляры.

Мои вопросы:

1) Правильно ли мое понимание?

Если это так;

2) Если новые объекты Point создаются Allocator, почему я вижу только две строки «Созданных точек»"?

Поскольку я ожидаю увидеть две строки для p1 и p2 , а также две строки для вновь созданного объекта с помощью Allocator.

3) Как Allocator присваивает исходные значения полям x, y вновь создаваемых объектов?Использует ли он необработанную копию памяти?

4) Является ли указатель общего доступа рекомендуемым способом возврата вектора из метода?

#include <iostream>
#include <vector>

using namespace std;

struct Point {
    Point() {
        std::cout<< "Point created\n";
        x=0;
        y=0;
    }
    int x;
    int y;
};


std::shared_ptr< vector<Point> > getPoints() {
    std::shared_ptr< vector<Point> > ret =  std::make_shared< vector<Point> >();
    Point p1;
    p1.x=100;
    p1.y=200;

    Point p2;
    p2.x = 1000;
    p2.y = 2000;

    ret->push_back(p1);
    ret->push_back(p2);

    return ret;
}

int main(int argc, char** argv)
{
    std::shared_ptr< vector<Point> > points = getPoints();
    for(auto point : *(points.get())) {
        std::cout << "Point x "<<point.x << " "<< point.y<<"\n";
    }

}

Ответы [ 3 ]

0 голосов
/ 04 октября 2018

Добавьте конструктор копирования в класс Point, чтобы увидеть, что происходит.

Point(const Point& p) {
    std::cout<< "Point copied\n";
    this->x = p.x;
    this->y = p.y;
}

Вы увидите, что оператор напечатан пять раз, если вы используете компилятор GCC.В функции getPoints, один раз для первого push_back, дважды для следующего push_back, поскольку размер вектора изменяется, и все элементы вставляются снова.Четвертый и пятый раз будут для цикла for в main.

. Вы можете удалить три копии , используя reserve, чтобы установить емкость vector вфункция getPoints,

ret->reserve(2);

и использование ссылок в цикле for main.

for(auto& point : *(points.get())) {
        std::cout << "Point x "<<point.x << " "<< point.y<<"\n";
}
0 голосов
/ 04 октября 2018

1) Правильно ли мое понимание?

[Ответ] Да, частично верно.Объекты p1 и p2 создаются в стеке, но при перемещении в вектор он вызывает конструктор копирования для создания и инициализации новых объектов.

2) Если новые объекты Point создаются Allocator, почему я вижу только две строки"Очки созданы"?Потому что я ожидаю увидеть две строки для p1 и p2, а также две строки для вновь созданного объекта с помощью Allocator.

[Ans] Использование конструктора копирования.Пожалуйста, добавьте конструктор копирования, и вы увидите разницу.

3) Как Распределитель присваивает исходные значения полям x, y вновь создаваемых объектов?Это использует сырую копию памяти?[Ответ] Использование конструктора копирования и самого вектора - это динамический массив, который перераспределяет память по мере необходимости.

4) Является ли совместно используемый указатель рекомендуемым способом возврата вектора из метода?[Ответ] Зависит от вашего варианта использования.Вы можете передать ссылку на вектор в качестве параметра и вернуть его.

0 голосов
/ 04 октября 2018

В: Мое понимание верно?

A: Ваше понимание частично верно.

  • p1 и p2 созданыв стеке, используя конструктор по умолчанию без аргументов, который вы определили.
  • Распределитель по умолчанию может использоваться для выделения большего объема памяти для p1 и p2 при вызове push_back (), но это не всегда происходит.Тем не менее, он никогда не создаст конструкцию по умолчанию для нового экземпляра Point.

В: Если новые объекты Point создаются Allocator, почему я вижу только две строки «Созданных точек»?

A: Новые объекты не создаются распределителем - распределитель только выделяет больше памяти, , если необходимо .Объекты, которые вы вставляете в вектор, создаются копией.Поскольку вы не создали конструктор копирования, компилятор сгенерировал его для вас.

В: Как Allocator присваивает исходные значения полям x, y вновь создаваемых объектов?Использует ли он необработанную копию памяти?

A: Как указано в предыдущем вопросе, распределитель только выделяет память и не создает и не уничтожает объекты.Поведение копирования полей выполняется конструктором копирования, который вызывается при выполнении push_back.Автоматически сгенерированный конструктор копирования выполнит построение копии каждого члена класса для каждого члена.В вашем случае x и y являются примитивными типами, поэтому они будут просто скопированы в необработанную память.Если бы члены были сложными объектами, их конструкторы копирования были бы вызваны.

Q: Является ли совместно используемый указатель рекомендуемым способом возврата вектора из метода?

A: Это будет зависеть от вашего варианта использования и основано на мнении.Мой личный совет, и это для всех видов объектов:

  • Если ваши варианты использования позволяют это, верните по значению (то есть std::vector<Point> getPoints())
  • Если вам нужнодинамически распределенное хранилище, или объект, который вы хотите вернуть, может быть ничем, потому что конструкция не удалась, вернуть на std::unique_ptr.Это относится практически ко всем фабричным функциям, которые вы, возможно, захотите создать.Даже если вы позже захотите поделиться владельцем (см. Пункт 3), вы можете создать shared_ptr, перейдя от unique_ptr (std::shared_ptr<T> shared = std::move(unique));
  • Избегайте использования shared_ptr, если вам действительно не нужен общий право собственности на птр.shared_ptr сложнее рассуждать, может создавать трудные для отладки циклы, приводящие к утечкам памяти, и тяжелее с точки зрения производительности (из-за атомарных операций, связанных с их повторным подсчетом и дополнительной выделенной памятью для блока управления).Если вы думаете, что вам нужен shared_ptr, пересмотрите свой дизайн и подумайте, можете ли вы использовать вместо него unique_ptr.

Как это работает:

Внутренне, std ::Vector использует память, выделенную с помощью распределителя по умолчанию (или пользовательского, если он был предоставлен) в куче.Это распределение происходит за кулисами и не зависит от размера вектора и от количества элементов в векторе (но всегда> = size()).Вы можете узнать, на сколько элементов вектор выделил память, используя функцию capacity().Когда вы вызываете push_back(), что происходит:

  1. Если имеется достаточно места (как определено capacity()) для хранения еще одного элемента, аргумент, который вы передали push_back, будет copyпостроен , с использованием конструктора копирования, если используется вариант push_back( const T& value ), или перемещен из, если используется push_back( T&& value ), с помощью конструктора перемещения.
  2. Если памяти больше нет (т. Е. Новый размер ()> емкость), выделяется больше памяти, которой будет достаточно для хранения новых элементов.Сколько памяти будет выделено, определяется реализацией.Обычный шаблон заключается в удвоении объема емкости, который вектор имел ранее, до порогового значения, после чего память выделяется в сегментах.Вы можете использовать reserve() перед вставкой элементов, чтобы убедиться, что ваш вектор будет иметь достаточную емкость, чтобы вместить как минимум столько же элементов без новых выделений.После выделения новой памяти вектор перераспределяет все существующие элементы в новое хранилище, либо копируя их, либо перемещая их, если они не вставляются при копировании.Это перераспределение сделает недействительными все итераторы и ссылки на элементы в векторе (предостережение: правила, когда именно копирование и перемещение будут использоваться, когда перераспределение немного сложнее, но это общий случай)
...