Проблемы с оператором C ++ 'new'? - PullRequest
1 голос
/ 14 апреля 2009

Я недавно встречал эту напыщенную речь .

Я не совсем понимаю некоторые из пунктов, упомянутых в статье:

  • Автор упоминает небольшое раздражение delete против delete[], но, похоже, утверждает, что это действительно необходимо (для компилятора), даже не предлагая решения. Я что-то пропустил?
  • В разделе 'Специализированные распределители' в функции f() кажется, что проблемы можно решить, заменив распределения на: (без выравнивания)

    // if you're going to the trouble to implement an entire Arena for memory,
    // making an arena_ptr won't be much work. basically the same as an auto_ptr,
    // except that it knows which arena to deallocate from when destructed.
    arena_ptr<char> string(a); string.allocate(80);
    // or: arena_ptr<char> string; string.allocate(a, 80);
    arena_ptr<int> intp(a); intp.allocate();
    // or: arena_ptr<int> intp; intp.allocate(a);
    arena_ptr<foo> fp(a); fp.allocate();
    // or: arena_ptr<foo>; fp.allocate(a);
    // use templates in 'arena.allocate(...)' to determine that foo has
    // a constructor which needs to be called. do something similar
    // for destructors in '~arena_ptr()'.
    
  • В «Опасности перегрузки :: operator new []» автор пытается сделать new(p) obj[10]. Почему бы не это (гораздо менее двусмысленно):

    obj *p = (obj *)special_malloc(sizeof(obj[10]));
    for(int i = 0; i < 10; ++i, ++p)
        new(p) obj;
    
  • 'Отладка выделения памяти в C ++'. Не могу поспорить здесь.

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

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

Кроме того, в случае, если я просто тупой и сумасшедший, в вашем идеальном C ++, что заменит operator new? Придумайте синтаксис по мере необходимости - что будет идеально , просто чтобы помочь мне лучше понять эти проблемы.

Ответы [ 2 ]

5 голосов
/ 14 апреля 2009

Ну, идеальный , вероятно, не требует удаления какого-либо типа. Иметь среду для сбора мусора, пусть программист избежит всей проблемы.

Кажется, что жалобы в разглагольствовании сводятся к

  1. "Мне понравилось, как это делает malloc"
  2. «Мне не нравится, когда меня заставляют явно создавать объекты известного типа»

Он прав насчет досадного факта, что вам нужно реализовать и new, и new[], но вы вынуждены сделать это из-за желания Страуструпса поддерживать ядро ​​семантики Си. Поскольку вы не можете отличить указатель от массива, вы должны сообщить компилятору самостоятельно. Вы можете это исправить, но это будет означать радикальное изменение семантики части языка C; Вы больше не можете использовать личность

*(a+i) == a[i]

, который сломал бы очень большое подмножество всего кода C.

Итак, вы можете иметь язык, который

  • реализует более сложное понятие массива и устраняет чудеса арифметики указателей, реализуя массивы с допинг-векторами или чем-то подобным.

  • - сборщик мусора, поэтому вам не нужна собственная дисциплина delete.

То есть вы можете скачать Java. Затем вы можете расширить это, изменив язык так, чтобы он

  • не является строго типизированным, поэтому проверка типа void * исключена,

... но это означает, что вы можете написать код, который преобразует Foo в Bar без компилятора. Это также включит ducktyping, если вы этого хотите.

Дело в том, что после того, как вы это сделали, у вас есть Python или Ruby с синтаксисом C-ish.

Я пишу на C ++ с тех пор, как Страуструп отправил ленты cfront 1.0; большая часть истории, связанной с C ++ в том виде, в каком она есть сейчас, проистекает из желания иметь ОО-язык, который мог бы вписаться в мир Си. В то же время появилось множество других, более приятных языков, таких как Эйфелева. С ++, кажется, победил. Я подозреваю, что он выиграл , потому что мог бы вписаться в мир C.

3 голосов
/ 15 апреля 2009

Напыщенная речь, ИМХО, очень вводит в заблуждение, и мне кажется, что автор действительно понимает более тонкие детали, просто он, кажется, хочет ввести в заблуждение. ИМХО, ключевой момент, который показывает недостаток аргумента, следующий:

void* operator new(std::size_t size, void* ptr) throw();

Стандарт определяет, что вышеуказанная функция обладает следующими свойствами:

Возвращает : ptr.

Примечания : Преднамеренно не выполняет никаких других действий.

Для подтверждения этого - эта функция намеренно не выполняет никаких других действий . Это очень важно, так как это ключ к тому, что делает размещение new: оно используется для вызова конструктора для объекта, и это все, что он делает. Обратите внимание, что параметр size даже не упоминается.

Для тех, у кого нет времени, чтобы подвести итог: все, что «malloc» делает в C, может быть сделано в C ++ с использованием «:: operator new». Разница лишь в том, что если у вас есть неагрегированные типы, т.е. типы, для которых нужно вызывать свои деструкторы и конструкторы, тогда вам нужно вызывать эти конструкторы и деструкторы. Такие типы явно не существуют в C, и поэтому использование аргумента «malloc делает это лучше» недопустимо. Если у вас есть структура в «C», которая имеет специальную функцию «initializeMe», которая должна вызываться с соответствующим «destroyMe», то все точки, сделанные автором, в равной степени относятся к этой структуре, как и к неагрегированной структуре C ++.

Явно взяв некоторые из его пунктов:

Чтобы реализовать множественное наследование, компилятор должен фактически изменять значения указателей во время некоторых приведений. Он не может знать, какое значение вы в конечном итоге захотите при преобразовании в void * ... Таким образом, ни одна обычная функция не может выполнять роль malloc в C ++ - нет подходящего возвращаемого типа.

Это не правильно, опять же :: оператор new выполняет роль malloc :

class A1 { };
class A2 { };
class B : public A1, public A2 { };

void foo () {
    void * v = ::operator new (sizeof (B));
    B * b = new (v) B();  // Placement new calls the constructor for B.
    delete v;

    v = ::operator new (sizeof(int));
    int * i = reinterpret_cast <int*> (v);
    delete v'
}

Как я уже упоминал выше, нам нужно размещение new, чтобы вызвать конструктор для B. В случае 'i' мы можем привести от void * к int * без проблем. , хотя снова использование размещения new улучшит проверку типов.

Еще одно замечание, которое он делает, касается требований к выравниванию:

Память, возвращаемая новым символом [...], не обязательно будет соответствовать требованиям выравнивания struct intlist.

Стандарт под 3.7.3.1/2 гласит:

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

Это мне кажется довольно ясным.

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

Что касается вызова деструктора, в C ++ вы можете легко создать специальный вид интеллектуального указателя, давайте назовем его «place_pointer », который мы можем определить для явного вызова деструктора, когда он выходит из области видимости. В результате мы могли бы иметь:

template <typename T>
class placement_pointer {
  // ...
  ~placement_pointer() {
    if (*count == 0) {
      m_b->~T();
    }
  }
  // ...
  T * m_b;
};

void
f ()
{
  arena a;

  // ...
  foo *fp = new (a) foo;           // must be destroyed
  // ...
  fp->~foo ();

  placement_pointer<foo> pfp = new (a) foo; // automatically !!destructed!!
  // ...
}

Последнее, что я хочу прокомментировать, это следующее:

g ++ поставляется с оператором "размещения" new [], определенным следующим образом:

inline void *
operator new[](size_t, void *place)
{
  return place;
}

Как отмечалось выше, не просто так реализовано - но это требуется стандартом.

Пусть obj будет классом с деструктором. Предположим, у вас где-то есть размер байта памяти (obj [10]) и вы хотите создать 10 объектов типа obj в этом месте. (C ++ определяет sizeof (obj [10]) равным 10 * sizeof (obj).) Можете ли вы сделать это с помощью этого оператора размещения new []? Например, следующий код выглядит так:

obj *
f ()
{
  void *p = special_malloc (sizeof (obj[10]));
  return new (p) obj[10];       // Serious trouble...
}

К сожалению, этот код неверен. В общем, нет никакой гарантии, что аргумент size_t, переданный оператору new [], действительно соответствует размеру выделяемого массива.

Но, как он подчеркивает, предоставляя определение, аргумент размера не используется в функции распределения. Функция выделения не выполняет ничего - и поэтому единственное влияние вышеприведенного выражения размещения - вызов конструктора для 10 элементов массива, как и следовало ожидать.

Есть другие проблемы с этим кодом, но не та, что указана автором.

...