Могут ли вызовы выделения памяти и конструктора чередоваться с другими операциями, необходимыми для выполнения «нового» выражения?

#c #class #constructor #new-operator

#c #класс #конструктор #new-operator

Вопрос:

Предположим, у меня есть следующий класс:

  class Sample {
 public:
     Sample( int ) {}
 };
  

некоторая функция, возвращающая int

 int SomeFunction()
{
    return 0;
}
  

и этот код:

 Sample* sample = new Sample( SomeFunction() );
  

Теперь я ожидаю следующую последовательность:

  • SomeFunction() выполняется, затем
  • ::operator new() выполняется для выделения памяти для объекта, затем
  • class Sample конструктор запускается через выделенную память

Этот порядок фиксирован или он может быть изменен реализацией таким образом, что, скажем, сначала выделяется память, затем SomeFunction() вызывается, затем запускается конструктор? Другими словами, могут ли вызовы operator new() функции и конструктора класса чередоваться с чем угодно?

Комментарии:

1. обязательно указана одна вещь, которая SomeFunction() будет вызываться всегда перед constructor Sample() . Таким образом, вопрос сузится только между SomeFunction() и operator new .

Ответ №1:

Порядок не указан. [5.3.4]/21 чтение:

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

Комментарии:

1. 1: Единственное, что должно соблюдаться, это то, что функция выделения должна возвращаться до вызова конструктора.

2. @Charles Bailey: Верно. Фактически единственными возможными последовательностями (при условии, что new не выдает) являются: a) оператор new, someFunction(), пример ctor b) someFunction(), оператор new, пример ctor

Ответ №2:

Порядок вызовов operator new и someFunction не указан, поэтому он может меняться в зависимости от настроек оптимизации, версии компилятора и т.д.

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

Ответ №3:

Да, это можно чередовать.

 class A
{
public:
    A(int i)
    {
        cout << "constructor" << endl;
    }
    void* operator new(size_t size)
    {
        cout << "new" << endl;
        return malloc(size);
    }
    void operator delete(void*, size_t)
    {
        cout << "delete" << endl;
    }
};

int f()
{
    cout << "f()" << endl;
    return 1;
}

int main()
{
    A* a = new A(f());
}

Output:
new
f()
constructor
  

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

Комментарии:

1. С каким компилятором это происходит?

2. С таким же успехом вы могли бы сказать, что если вычисление аргумента выдает исключение, то конструктор вообще не будет вызван, и поэтому вызов new слишком рано — плохая идея!

Ответ №4:

На самом деле, я думаю, происходит следующее:

  • new используется для выделения необработанной памяти
  • Вызывается someFunction(), возвращающая значение X
  • вызывается конструктор с X в качестве параметра a

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

Комментарии:

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

Ответ №5:

Вы не можете изменить то, что происходит при запуске этой строки кода. Вы можете запускать несколько разных строк кода.

 void * p = ::operator new (sizeof (SomeFunction));
SomeFunction temp;
SomeFunction* sample = new (p) SomeFunction(temp);