Можно ли выполнить присваивание до вызова конструктора? - PullRequest
2 голосов
/ 18 июня 2009

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

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

Давайте рассмотрим код:

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

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

Ответы [ 4 ]

1 голос
/ 18 июня 2009

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

void Test()
{
    A *temp = new A;
    MemoryWriteBarrier(); // use whatever memory barrier your platform offers
    a = temp;
}
1 голос
/ 18 июня 2009

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

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

A()
{
   member = 7;
}

Потом позже:

singleton = new A()

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

1 голос
/ 18 июня 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.

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

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

0 голосов
/ 18 июня 2009

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

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

см

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

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

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

...