Подтвердить что ты не робот

Использование destructor = delete;

Рассмотрим следующий класс:

struct S { ~S() = delete; };

Вскоре и с целью вопроса: я не могу создавать экземпляры S как S s{};, потому что я не мог их уничтожить.
Как упоминалось в комментариях, я все еще могу создать экземпляр, выполнив S *s = new S;, но я не могу его удалить.
Поэтому единственное, что я могу увидеть для удалённого деструктора, выглядит примерно так:

struct S {
    ~S() = delete;
    static void f() { }
};

int main() {
    S::f();
}

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

Каковы другие применения (если они есть) удаленного деструктора?

4b9b3361

Ответ 1

Если у вас есть объект, который никогда не должен быть delete d или храниться в стеке (автоматическое хранилище) или храниться как часть другого объекта, =delete будет препятствовать всем этим.

struct Handle {
  ~Handle()=delete;
};

struct Data {
  std::array<char,1024> buffer;
};

struct Bundle: Handle {
  Data data;
};

using bundle_storage = std::aligned_storage_t<sizeof(Bundle), alignof(Bundle)>;

std::size_t bundle_count = 0;
std::array< bundle_storage, 1000 > global_bundles;

Handle* get_bundle() {
  return new ((void*)global_bundles[bundle_count++]) Bundle();
}
void return_bundle( Handle* h ) {
  Assert( h == (void*)global_bundles[bundle_count-1] );
  --bundle_count;
}
char get_char( Handle const* h, std::size_t i ) {
  return static_cast<Bundle*>(h).data[i];
}
void set_char( Handle const* h, std::size_t i, char c ) {
  static_cast<Bundle*>(h).data[i] = c;
}

Здесь у нас непрозрачный Handle, который не может быть объявлен в стеке или динамически распределен. У нас есть система, чтобы получить их из известного массива.

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

И интерфейс не должен раскрывать, как работает Bundle. Просто непрозрачный Handle.

Теперь этот метод может быть полезен, если другие части кода должны знать, что все Ручки находятся в этом конкретном буфере, или их продолжительность жизни отслеживается определенными способами. Возможно, это также можно было бы использовать с частными конструкторами и функциями друга factory.

Ответ 2

одним из сценариев может быть предотвращение неправильного освобождения:

#include <stdlib.h>

struct S {
    ~S() = delete;
};


int main() {

    S* obj= (S*) malloc(sizeof(S));

    // correct
    free(obj);

    // error
    delete obj;

    return 0;

}

это очень рудиментарно, но применимо к любому специальному процессу выделения/освобождения (например, a factory)

пример 'С++'

struct data {
    //...
};

struct data_protected {
    ~data_protected() = delete;
    data d;
};

struct data_factory {


    ~data_factory() {
        for (data* d : data_container) {
            // this is safe, because no one can call 'delete' on d
            delete d;
        }
    }

    data_protected* createData() {
        data* d = new data();
        data_container.push_back(d);
        return (data_protected*)d;
    }



    std::vector<data*> data_container;
};

Ответ 3

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

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

#include <iostream>

struct S { 
    const char* mx;

    const char* getx(){return mx;}

    S(const char* px) : mx(px) {}
    ~S() = delete; 
};

int main() {
    char *buffer=new char[sizeof(S)];
    S *s=new(buffer) S("not deleting this...");//Constructs an object of type S in the buffer.
    //Code that uses s...
    std::cout<<s->getx()<<std::endl;

    delete[] buffer;//release memory without requiring destructor call...
    return 0;
}

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

Если автоматически созданный деструктор сделает что-то нетривиальное, вы, скорее всего, скомпрометируете действительность вашей программы, не выполнив ее семантику.

Предоставление программы оставить main() и разрешить "очистку" среды - это правильная техника, но ее лучше избегать, если только ограничения не являются строго необходимыми. В лучшем случае это отличный способ маскировать подлинную утечку памяти!

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

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

Существует понятие статического класса (без конструкторов) и поэтому логически не требует деструктора. Но такие классы более целесообразно реализованы, поскольку namespace не имеют (хорошего) места в современном С++, если не установлены шаблоны.

Ответ 4

Зачем отмечать деструктор как delete?

Чтобы избежать вызова деструктора, конечно;)

Каковы варианты использования?

Я вижу как минимум три разных применения:

  • Класс никогда не должен создаваться; в этом случае я также ожидал бы удаленный конструктор по умолчанию.
  • Экземпляр этого класса должен быть просочился; например, экземпляр одиночного каротажа
  • Экземпляр этого класса может быть создан и удален только определенным механизмом; это может произойти при использовании FFI

Чтобы проиллюстрировать последнюю точку, представьте себе интерфейс C:

struct Handle { /**/ };

Handle* xyz_create();
void xyz_dispose(Handle*);

В С++ вы хотели бы обернуть его в unique_ptr для автоматизации выпуска, но что, если вы случайно напишете: unique_ptr<Handle>? Это катастрофа во время выполнения!

Итак, вы можете настроить определение класса:

struct Handle { /**/ ~Handle() = delete; };

а затем компилятор запустится на unique_ptr<Handle>, заставив вас правильно использовать unique_ptr<Handle, xyz_dispose>.

Ответ 5

Создание экземпляра объекта с помощью new и никогда не удаление его - это самый безопасный способ реализации Синглтона С++, поскольку он позволяет избежать любых проблем с порядком уничтожения. Типичным примером этой проблемы будет "Single Logging" ( "Logging" Singleton), к которому обращаются в деструкторе другого класса Singleton. Alexandrescu когда-то посвятил целый раздел своей классической книги "Modern С++ Design" о путях решения проблем порядка уничтожения в реализациях Singleton.

Удаленный деструктор хорош, чтобы даже сам класс Singleton не мог случайно удалить экземпляр. Это также предотвращает сумасшедшее использование, например delete &SingletonClass::Instance() (если Instance() возвращает ссылку, как и должно быть, нет причин для возврата указателя).

В конце концов, ничего из этого не примечательно. И, конечно же, вы вообще не должны использовать синглтоны.