Как предотвратить создание объекта в куче?

Кто-нибудь знает, как я могу в независимом от платформы коде C++ предотвратить создание объекта в куче? То есть для класса «Foo» я хочу запретить пользователям делать это:

Foo *ptr = new Foo;

и только позволить им это сделать:

Foo myfooObject;

У кого-нибудь есть идеи?

Овации,

9 ответов

  1. Внутри класса Foo можно объявить функцию operator new, которая заблокирует доступ к нормальной форме new.

    Это то поведение, которое вы хотите ?

  2. Вы можете перегрузить новое для Foo и сделать его частным. Это означало бы, что компилятор будет стонать… если вы не создаете экземпляр Foo в куче из Foo. Чтобы поймать этот случай, вы можете просто не писать новый метод Foo, и тогда компоновщик будет стонать о неопределенных символах.

    class Foo {
    private:
      void* operator new(size_t size);
    };
    

    ПС. Да, я знаю, что это можно легко обойти. Я действительно не рекомендую его — я думаю, что это плохая идея — я просто отвечал на вопрос!

  3. Не уверен, что это дает какие-либо возможности во время компиляции, но вы рассматривали перегрузку «нового» оператора для вашего класса?

  4. Я не знаю, как это сделать надежно и портативно.. но.

    Если объект находится в стеке, то в конструкторе можно утверждать, что значение ‘this’ всегда близко к указателю стека. Есть хороший шанс, что объект будет в стеке, если это так.

    Я считаю, что не все платформы реализуют свои стеки в одном направлении, поэтому вы можете сделать одноразовый тест, когда приложение начнет проверять, каким образом стек растет.. Или сделать какую-то выдумку:

    FooClass::FooClass() {
        char dummy;
        ptrdiff_t displacement = &dummy - reinterpret_cast<char*>(this);
        if (displacement > 10000 || displacement < -10000) {
            throw "Not on the stack - maybe..";
        }
    }
    
  5. @Ник

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

    Например:

    struct MyStruct {
        Foo m_foo;
    };
    
    MyStruct* p = new MyStruct();
    

    Здесь я создал экземпляр ‘ Foo ‘ в куче, минуя скрытый новый оператор Foo.

  6. Вы можете объявить его как интерфейс и управлять классом реализации более непосредственно из собственного кода.

  7. Ответ Ника является хорошей отправной точкой, но неполной, так как вам на самом деле нужно перегрузить:

    private:
        void* operator new(size_t);          // standard new
        void* operator new(size_t, void*);   // placement new
        void* operator new[](size_t);        // array new
        void* operator new[](size_t, void*); // placement array new
    

    (Хорошая практика кодирования предполагает, что вы также должны перегрузить операторы delete и delete [] — я бы, но так как они не будут называться, это действительно не нужно.

    Pauldoo также верно, что это не выживет агрегирования на Foo, хотя он действительно выживет наследования от Foo. Вы могли бы сделать некоторые шаблоны метапрограммирования магии, чтобы помочь предотвратить это, но это не будет иммунитетом к «злым пользователям» и, таким образом, вероятно, не стоит усложнения. Документация о том, как он должен использоваться, и обзор кода, чтобы убедиться, что он используется должным образом, являются единственным ~100% способом.

  8. Поскольку отладочные заголовки могут переопределять новую подпись оператора, лучше всего использовать … подписи как полное средство правовой защиты:

    private:
    void* operator new(size_t, ...) = delete;
    void* operator new[](size_t, ...) = delete;
    
  9. это можно предотвратить, сделав конструкторы частными и предоставив статический элемент для создания объекта в стеке

    Class Foo
    {
        private:
            Foo();
            Foo(Foo& );
        public:
            static Foo GenerateInstance() { 
                Foo a ; return a; 
            }
    }
    

    это сделает создание объекта всегда в стеке.