Копирование c ++: почему это неприменимо для использования с непосвященным объектом? - PullRequest
0 голосов
/ 03 мая 2018

Я читаю великую книгу BJARNE STROUSTRUP: Программирование: принципы и практики с использованием программирования на C ++. Я читаю ту часть, в которой он пытается показать, как создать вектор. Однако одно предложение поймало меня. (Я могу указать вам местоположение этого предложения во всей книге, если вы также читаете эту книгу, что маловероятно, поэтому я не нахожу ее здесь.)

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

Чтобы помочь вам получить контекст, я приведу здесь несколько кодов:

template<typename T, typename A = allocator<T>> class my_vector {
          A alloc;             // use allocate to handle memory for elements
          // . . .
private : 
    T* elem;
    int sz;//short for size
    int space;//actually means capacity here.
};

Он пытается показать, как реализовать reserve(). Реализация здесь:

template<typename T, typename A>
void my_vector<T,A>::reserve(int newalloc)
{
          if (newalloc<=space) return;               // never decrease allocation
          T* p = alloc.allocate(newalloc);          // allocate new space
     /***/for (int i=0; i<sz; ++i) alloc.construct(&p[i],elem[i]);// copy!!! he means we can't use copy assignment operator here!
          for (int i=0; i<sz; ++i) alloc.destroy(&elem[i]);                 // destroy
          alloc.deallocate(elem,space);             // deallocate old space
          elem = p;
          space = newalloc;
}

Он означает, что мы можем только скопировать конструкцию elem, используя alloc.construct(), вместо использования простого назначения копирования, такого как p[i] = elem[i]. И причина, по которой он приводит это то, что я цитировал выше.

Предполагая, что назначение копирования определено для универсального типа T (возможно, с использованием Concepts), почему назначение копирования не может использоваться здесь до сих пор? Он говорит

"например, string, assignment предполагает, что целевая область была инициализирована".

Однако я все равно не понимаю. Можете ли вы помочь мне понять его суть? Я думаю, что назначение копирования работает хорошо, даже если цель не инициирована, в конце концов, цель - это та, которая должна быть инициирована.

Спасибо!

Ответы [ 3 ]

0 голосов
/ 05 мая 2018

Иногда назначение копирования должно предполагать, что цель инициализирована. Например, vector. При назначении vector другому vector, ресурсы в назначенном vector должны быть delete, и это неизбежно предполагает, что цель инициализирована, потому что мы должны ссылаться на указатель на член.

0 голосов
/ 05 мая 2018

Вектор слишком продвинут. Я уверен, что несколько сотен страниц предшествуют этому примеру. Итак, если вы внимательно прочитали книгу, вы, должно быть, заметили разницу между присваиванием и инициализацией (более формально построением). Строительство похоже на строительство гаража, а назначение - на парковку автомобиля в гараже. Как вы должны поставить машину в незастроенный гараж?

T* p = alloc.allocate(newalloc);

Это просто распределяет пространство, объект еще не был построен (Гаража нет). Таким образом, назначение не применимо (хотите оставить свой автомобиль на краю опасности?). Таким образом, следующим шагом является создание объектов, и лучший выбор - это копирование / перемещение. Можно создать конструкцию по умолчанию (построить пустые гаражи), а затем присвоить объектам (парковать автомобили), но более эффективно копировать / перемещать конструкцию и с точки зрения разработки библиотеки шаблонов, также менее требовательную. Помните, что распределители ведут себя не так, как new, так как последний автоматически создает объект (ы) сразу после выделения, а первый разделяет 2 фазы.

0 голосов
/ 03 мая 2018

Допустим, у вас есть класс, как

struct Foo
{
    int * data;
    int size
    Foo() : data(nullptr), size(0) {}
    Foo(const Foo& f) : data(new int[f.size]), size(f.size) 
    { 
        std::copy(f.data, f.data + f.size, data);
    }
    Foo& operator=(const Foo& f)
    {
        if (&f == this)
            return *this;
        else
        {
            if (size < f.size)
                //allocate new space
            // copy
        }
    }
};

Теперь, если мы заменим

for (int i=0; i<sz; ++i) alloc.construct(&p[i],elem[i]);

с

for (int i=0; i<sz; ++i) p[i] = elem[i];

Это не сработает. p[i] относится к Foo, который никогда не был построен. Это означает, что нет, если его члены были установлены, что означает, что когда мы делаем if (size < f.size), мы обращаемся к неинициализированной переменной, и это неопределенное поведение.

Вот почему мы делаем копию (или перемещаемся в C ++ 11 и выше). Это гарантирует, что объект сконструирован и что «правильная вещь» случится.

...