Можно ли выполнить присвоение до вызова конструктора?

Комментарий к Что не так с этим исправлением для блокировки с двойной проверкой? говорит:

Проблема в том, что переменная может быть назначена до запуска (или завершения) конструктора, а не до выделения объекта.

Рассмотрим код:

A *a;

void Test()
{
    a = new A;
}

Чтобы обеспечить более формальный анализ, давайте разделим a = new A на несколько операций:

void *mem = malloc(sizeof(A)); // Allocation
new(mem) A; // Constructor
a = reinterpret_cast<A *>(mem); // Assignment

Верен ли приведенный выше комментарий и в каком смысле? Может ли конструктор быть выполнен после присвоения? Если может, что можно сделать против этого, если необходим гарантированный порядок из-за безопасности МТ?


person Suma    schedule 17.06.2009    source источник
comment
На самом деле, с этим ничего нельзя сделать. Вам нужно будет сделать любое чтение и любую запись изменчивой, а также сделать изменчивой память, чтобы все было в порядке, в котором ваш код записывает ее, и чтобы все эти записи / чтения были разделены точкой последовательности. Но это все равно ничего не сделает с многопоточностью: стандарт об этом не знает.   -  person Johannes Schaub - litb    schedule 18.06.2009
comment
Например, int volatile a, b; а = 1; b = 2; эти два не могут быть переупорядочены, а в int volatile a; int b; а = 1; b = 2; тогда эти можно переупорядочить, потому что b не является изменчивым. И в f (a = 1, b = 2); Затем их тоже можно переупорядочить, потому что в нем нет никакого порядка. Я считаю, что статья Александреску и Скотт Мейерс довольно хорошо объясняет это, имхо   -  person Johannes Schaub - litb    schedule 18.06.2009
comment
См .: общее неопределенное поведение stackoverflow.com/questions/367633/   -  person Martin York    schedule 18.06.2009


Ответы (4)


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

Предположим:

A()
{
   member = 7;
}

Тогда позже:

singleton = new A()

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

person Michael    schedule 17.06.2009

Я думаю, что следующее должно работать:

void Test()
{
    A *temp = new A;
    MemoryWriteBarrier(); // use whatever memory barrier your platform offers
    a = temp;
}
person Suma    schedule 17.06.2009

a - это глобальный объект со статической продолжительностью хранения, поэтому он будет инициализирован в некотором заранее выделенном хранилище где-то до выполнения тела main. Если предположить, что вызов Test не является результатом какой-то странности конструкции статического объекта, a будет полностью построен к моменту вызова Test.

a = new A;

Это немного необычное присваивание не будет (только) стандартной операцией присваивания копии, поскольку вы назначаете указатель на A на a, а не на объект или ссылку. Собирается ли он на самом деле и что именно он вызывает, зависит от того, есть ли у A оператор присваивания, который принимает указатель на A, или что-то неявно конвертируемое из указателя на A, или есть ли у A неявный конструктор, который принимает указатель на A (или указатель на базовый класс A).

Разместите редактирование, ваш код делает нечто иное!

Концептуально это выглядит примерно так:

A *tmpa;
void *mem = ::operator new( sizeof(A) ); // ( or possibly A::operator new )

try
{
    tmpa = new (mem) A; // placement new = default constructor call
}
catch (...)
{
    ::operator delete( mem );
    throw;
}

a = tmpa; // pointer assignment won't throw.

Опасность написания чего-то вроде этого заключается в том, что вы неявно добавляете много точек последовательности, которых просто нет в оригинале, и, кроме того, компилятору разрешено генерировать код, который не выглядит так, пока он ведет себя "как если бы" это было написано этим, насколько могла определить исполняющая программа. Это правило «как если бы» применяется только к исполняющемуся потоку, поскольку (текущий) язык ничего не говорит о работе взаимодействия с другими потоками.

Для этого вам необходимо использовать определенные гарантии поведения (если таковые имеются), предоставляемые вашей реализацией.

person CB Bailey    schedule 17.06.2009
comment
Ваш ответ основан на моем коде, содержащем опечатку (отсутствует *). Извините, код исправлен. - person Suma; 18.06.2009

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

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

видеть

«C ++ и опасности двойной проверки блокировки»

Скотт Мейерс и Андрей Александреску

http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

person Alex Brown    schedule 17.06.2009