Почему защищенные С++-Cli-деструкторы вызывают ошибки компиляции?

Если я компилирую и запускаю следующее:

using namespace System;

ref class C1
{
public:
    C1()
    {
        Console::WriteLine(L"Creating C1");
    }

protected:
    ~C1()
    {
        Console::WriteLine(L"Destroying C1");
    }
};

int main(array<System::String ^> ^args)
{

    C1^ c1 = gcnew C1();
    delete c1;

    return 0;
}

... код компилируется без ошибок и запускает это:

Creating C1
Destroying C1
Press any key to continue . . .

Если я делаю то же самое в С++, я получаю сообщение об ошибке в следующих строках:

1>ProtectedDestructor.cpp(45): error C2248: 'C1::~C1' : cannot access protected member declared in class 'C1'
1>          ProtectedDestructor.cpp(35) : compiler has generated 'C1::~C1' here
1>          ProtectedDestructor.cpp(23) : see declaration of 'C1'

... так почему это действительно в CLI?

Ответ 1

Это проблема с аблацией. В С++/CLI есть несколько из них, мы уже рассмотрели проблему ключевого слова const. То же самое здесь, среда выполнения не имеет понятия деструктора, только финализатор является реальным. Поэтому его нужно подделать. Было очень важно создать эту иллюзию, шаблон RAII в родном С++ свят.

Это подделка, закрепив понятие деструктора поверх интерфейса IDisposable. Тот, который делает детерминированное разрушение, работает в .NET. Очень часто ключевое слово using на языке С# вызывает его, например. Нет такого ключевого слова в С++/CLI, вы используете оператор delete. Так же, как в родном С++. А компилятор помогает автоматически испускать вызовы деструктора при использовании семантики стека. Так же, как и у компилятора С++. Спасение RAII.

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

public ref class Foo : IDisposable {
protected:
    //~Foo() {}
    virtual void Dispose() = IDisposable::Dispose {}
};

Создает очень впечатляющий список ошибок, когда вы пытаетесь это сделать, компилятор борется так сильно, как может:). C2605 является единственным релевантным: "Dispose": этот метод зарезервирован в управляемом классе ". Он не может поддерживать иллюзию, когда вы это делаете.

Короче говоря, реализация метода IDisposable:: Dispose() всегда является общедоступной, независимо от доступности деструктора. Оператор delete вызывает его. Нет обходного пути для этого.

Ответ 2

В дополнение к подробному ответу Ханса, что delete в объекте С++/CLI фактически является активацией интерфейса IDisposable, а наследование интерфейсов всегда открыто 1 может быть полезно спросить

Как вызывается защищенный деструктор, тогда?

Созданные компилятором методы Dispose вызывают определяемый пользователем деструктор. Поскольку этот метод Dispose является членом класса, он имеет доступ к членам класса protected и private, таким как деструктор.

(В исходном С++ компилятор не подчиняется правилам доступности, поскольку он является одним из них. В .NET верификатор IL обеспечивает их выполнение.)


1 Собственно, его объяснение сосредоточено на том, что компилятор не допускает явной реализации IDisposable::Dispose(), и в этом случае он может быть частным членом. Но это совершенно неуместно. Элементы virtual могут быть доступны через тип объявления. И delete не вызывает object->Dispose(), он вызывает safe_cast<IDisposable^>(object)->Dispose().