Предотвращение запуска деструктора в C ++ - PullRequest
3 голосов
/ 03 февраля 2012

Я бы хотел убедиться, что деструктор объекта не работает , а .Есть ли способ сделать это, кроме как положить объект в кучу и не вызывать delete?

Ответы [ 5 ]

7 голосов
/ 03 февраля 2012

Не рекомендуется , но есть и другой способ:

char buffer[sizeof(T) + alignof(T)];
char* aligned_buffer = buffer + alignof(T) - reinterpret_cast<intptr_t>(buffer) % alignof(T);
T* object = new (aligned_buffer) T;

Однако, хотя это означает, что компилятор не будет автоматически вызывать деструктор, программист все равно может:

object->~T();

Конечно, это верно и для распределения кучи:

delete object;

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

struct T
{
private:
   ~T() {};
};

Или действительно недоступным(связанный: Почему деструктор базового класса должен быть доступен только при объявлении пользовательского конструктора? ):

class indestructible_base
{
    ~indestructible_base();
};

struct T : indestructible_base
{
};
3 голосов
/ 12 ноября 2018

Я удивлен, что никто не упомянул полностью совместимое со стандартом C ++ решение с union. В union ни один конструктор, даже деструктор, не вызывается автоматически для членов. Даже если в союзе только один член. Все что нужно сделать "вручную":

  • конструктор можно вызвать через так называемое «размещение нового» (или из списка инициализации конструктора, введенного :),

  • деструктор может быть вызван явным вызовом метода деструктора.

Демо-версия:

class MyObj {
  int i;
public:
  MyObj(int i_) : i(i_) { std::cout << i << " constructed" << std::endl; }
  MyObj(MyObj const &src) : i(src.i + 100) { std::cout << src.i << " copied to new " << i << std::endl; }
  ~MyObj() { std::cout << i << " destroyed" << std::endl; }
};

class OptDestr {
  bool callDestructor;
  union { MyObj o; };  // Only allocated, but no constr/destr is called automatically

public:
  // Constructor
  OptDestr(int i, bool callDestructor_) : callDestructor(callDestructor_), 
    o(i) // calls MyObj constructor
  { }
  // OR alternatively:
  OptDestr(int i, bool callDestructor_) : callDestructor(callDestructor_) {
    new (&o)MyObj(i);  // placement new - does NOT allocate, just calls constructor
  }

  // Copy constructor
  OptDestr(OptDestr const &src) : callDestructor(src.callDestructor),
    o(src.o)  // explicit call of MyObj copy-constructor
  { }
  // OR alternatively:
  OptDestr(OptDestr const &src) : callDestructor(src.callDestructor) {
    new (&o)MyObj(src.o);  // placement new - no allocation, just explicitly calls copy-constructor of MyObj
  }

  // Destructor
  ~OptDestr() {
    if (callDestructor) o.~MyObj();  // explicit call of destructor
  }
};


int main() {
  OptDestr d1(1, false /*callDestructor*/);
  OptDestr d1_copy(d1);
  OptDestr d2(2, true /*callDestructor*/);
  OptDestr d2_copy(d2);
}

Вывод программы следующий:

1 constructed
1 copied to new 101
2 constructed
2 copied to new 102
102 destroyed
2 destroyed

Вы можете видеть, что нет ни 1 destructed, ни 101 destructed. Я предполагаю, что типы типа std::Optional реализованы аналогичным образом.

2 голосов
/ 03 февраля 2012

Если вы хотите, чтобы при вызове деструктора произошла ошибка, просто не предоставляйте ее определение:

struct foo {
  ~foo();
};

или delete в C ++ 11:

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

Если вы хотите, чтобы он иногда вызывался только тогда, вам почти наверняка нужно переосмыслить свой дизайн.Не существует «безопасного» способа сделать это, и хотя использование new без удаления может сработать, я настоятельно рекомендую вам этого не делать.

В качестве альтернативы вы можете добавить флаг, если вам иногда требуется определенное поведение деструктора:

struct foo {
  foo(bool destroy = true) : destroy(destroy) {}
  ~foo() {
    if(destroy) {
      // destruction stuff here
    }
  }
  bool destroy;
};
1 голос
/ 18 августа 2018

Следуя принципам работы std :: необязательный , вы можете создать свой собственный более простой шаблон обертки объектов:

#include <iostream>

template <typename T_>
struct Wrapper
{
    union {
        char dummy_;
        T_ value_;
    };
    bool shouldDestroy_ {true};

    template<typename ...Args>
    Wrapper(Args &&... args) :
        value_(std::forward<Args>(args)...)
    {
    }

    const T_ &value() const { return value_; }
    T_ &value() { return value_; }

    const T_ *get() const { return &value_; }
    T_ *get() { return &value_; }

    const T_ *operator->() const { return get(); }
    T_ *operator->() { return get(); }

    void destroy(bool destroy) { shouldDestroy_ = destroy; }

    ~Wrapper()
    {
        if (shouldDestroy_) {
            value_.~T_();
        }
    }

};

int main()
{
    struct Foo
    {
        Foo() { std::cout << "Foo()\n"; }
        Foo(int value) { std::cout << "Foo(" << value << ")\n"; }
        ~Foo() { std::cout << "~Foo()\n"; }

        void display(const char *str) { std::cout << str << "\n"; }
    };

    std::cout << "Running...\n";

    // example 1 construct/destructed    
    {
        Wrapper<Foo> a;
        a.value().display("via value 1");
        a->display("via operator 1");
    }

    // example 2 construct/destructed    
    {
        Wrapper<Foo> a(2);
        a.value().display("via value 2");
        a->display("via operator 2");
    }

    // example 3 construct NOT destructed    
    {
        Wrapper<Foo> a(3);
        a.value().display("via value 3");
        a->display("via operator 3");

        // do not allow destroy
        a.destroy(false);
    }

    return 0;
}

И вывод будет следующим:

Running...
Foo()
via value 1
via operator 1
~Foo()
Foo(2)
via value 2
via operator 2
~Foo()
Foo(3)
via value 3
via operator 3
1 голос
/ 03 февраля 2012

Другим способом является placement new оператор.

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