2010-12-12 4 views
5

Я понимаю, что это опрометчиво, и я не предлагаю, чтобы сделать это, но мне интересно, является ли следующее фактически формально незаконны:Ручно имитирует влияние оператора delete на C++ формально незаконным?

#include <iostream> 

struct X 
{ 
    ~X() 
    { 
     std::cout << "~X()\n"; 
    } 
}; 

int main() 
{ 
    X *x = new X; 
    //delete x; 
    x->~X(); 
    ::operator delete(x); 
    return 0; 
} 

Это мое понимание того, что delete x; эквивалентно вызывая деструктор, а затем вызывая ::operator delete(x);, но разве это законно для меня делать это вручную в соответствии со стандартом? Я знаю, что это правильная вещь при использовании нового места размещения, но как насчет случая без места размещения? Моя догадка в том, что это может быть незаконно, потому что delete (а не operator delete) должно выполняться для каждого new, но мне было бы интересно узнать наверняка.

+0

Вы спрашиваете, является ли 'delete x;' эквивалентным вызову деструктора, а затем 'operator delete (x);' для * данного конкретного примера кода * или * вообще *? –

+0

@Ben: В этом конкретном примере кода - но меня также интересует общий случай (для которого я теперь предполагаю, что ответ отрицательный). –

+0

Рад, что я задал этот вопрос - на самом деле есть гораздо больше ответа, чем я представлял. –

ответ

3

Я уверен, что это не соответствует стандарту. Нигде в стандарте (что я знаю) это говорит о том, что операнд delete p; передается непосредственно функции дезактивации, а для delete [] он почти наверняка не прошел без изменений.

Однако это, вероятно, будет работать на всех практических реализациях.

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

Кроме того, в общем случае указатель, переданный функции освобождения, определенно не является операндом удаления. Рассмотрим:

struct A 
{ 
    virtual ~A() {} 
}; 

struct B1 : virtual A {}; 

struct B2 : virtual A {}; 

struct B3 : virtual A {}; 

struct D : virtual B1, virtual B2, virtual B3 {}; 

struct E : virtual B1, virtual D {}; 

int main(void) 
{ 
    B3* p = new D(); 
    p->~B3(); // should be ok, calling virtually 
    operator delete(p); // definitely NOT OK 

    return 0; 
} 
+0

+1, я думаю, что я определенно согласен с аспектом «не должен» всего этого. Ребята из comp.lang.C++ также упомянули, что пользовательский «оператор новый» и «оператор delete» проблемы, поэтому кажется, что это глупая вещь, даже если она легальна. Что касается вопроса синтаксиса - я знаю, что вы можете вызвать 'T :: operator delete', чтобы получить T-класс, если он существует. Там может быть какое-то метапрограммирование шаблона, которое можно сделать, чтобы убедиться, что глобальный называется иначе, но я не эксперт в этом. Вероятно, это что-то вроде SFINAE, если можно, я думаю. –

+1

«Я уверен, что это не стандартное соответствие.» «Вы имеете в виду, что ** конкретный код ** в вопросе не гарантированно работает? Вы имеете в виду, что 'new X' не может вызывать' operator new (sizeof (X)) '? – curiousguy

+0

@curiousguy; Я имею в виду, что «новому X» разрешено иметь другие побочные эффекты, помимо вызова функции распределения и конструктора, для которых требуется более позднее совпадение с выражением удаления. –

2

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

+0

Спасибо - это не окончательный ответ, который я искал, но я ценю мнение. Я спросил об этом на comp.lang.C++, если кто-нибудь может просветить меня главой и стихом из стандарта. –

+1

Позвоните деструктору и освободите память, да. Но вы должны освободить правильную память, вызвав правильную функцию освобождения * и *, передающую правый указатель. Что в LOT более сложное в общем случае, чем предлагает код в этом вопросе. –

0

Я думал, что

::operator delete(x); 

такой же, как

delete x; 

если метод delete не переопределен X?

+2

Это не так. Оператор 'delete' вызывает деструктор, если указатель не является нулевым, а затем вызывает функцию освобождения, если указатель не равен нулю и, возможно, даже если он есть. Параметр, переданный функции освобождения, не указан в стандарте. –

3

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

X *x = new X; 

по

X* x = static_cast<X*>(::operator new(sizeof(X))); 

try { new(x) X; } 
catch (...) { ::operator delete(x); throw; } 

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

+0

Спасибо - это использование нового места, хотя (я знаю, что это действительно). Мне было любопытно, нельзя ли смешивать и сопоставлять их. –

+0

Я уверен, что это именно то, что делает 'new' - если не происходит перегрузка' new'. – curiousguy

1

@StuartGolodetz

Ответ, и ответ на комментарий

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

 
struct B { 
    virtual ~B(); 
}; 

struct D : B { 
    operator new (size_t); 
    operator delete (void*); 
}; 

B *p = new D; 

decltype(typeid(*p))::operator delete (...); // hypothetical C++++ 

Meta это!

+0

Используется статический тип '* p', который является' B', правильно? И, таким образом, вызывает неправильное освобождение? На самом деле он даже не должен компилироваться, потому что 'B' не имеет оператора' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' –

+0

@BenVoigt Hum, вы взяли его буквально? '* p ::' это даже не C++! – curiousguy

+0

Я полагаю, что нет, но 'decltype (* p) ::' is. –