Кто-нибудь знает, как я могу в независимом от платформы коде C++ предотвратить создание объекта в куче? То есть для класса «Foo» я хочу запретить пользователям делать это:
Foo *ptr = new Foo;
и разрешить им делать это только:
Foo myfooObject;
У кого-нибудь есть какие-либо идеи?
Ваше здоровье,
Обратное, что тоже может быть интересно читателям: stackoverflow.com/questions/124880/…
Вы можете объявить функцию под названием «operator new» внутри класса Foo, которая заблокировала бы доступ к нормальной форме new.
Вы хотите такого поведения?
Вы можете перегрузить новый для Foo и сделать его закрытым. Это будет означать, что компилятор будет стонать ... если вы не создаете экземпляр Foo в куче из Foo. Чтобы поймать этот случай, вы можете просто не писать новый метод Foo, и тогда компоновщик будет стонать о неопределенных символах.
class Foo {
private:
void* operator new(size_t size);
};
PS. Да, я знаю, что это легко обойти. Я действительно не рекомендую это - я думаю, что это плохая идея - я просто отвечал на вопрос! ;-)
Не уверен, предлагает ли это какие-либо возможности во время компиляции, но смотрели ли вы на перегрузку оператора 'new' для своего класса?
Я не знаю, как это сделать надежно и портативно .. но ..
Если объект находится в стеке, вы можете утверждать в конструкторе, что значение this всегда близко к указателю стека. В этом случае велика вероятность того, что объект окажется в стеке.
Я считаю, что не все платформы реализуют свои стеки в одном и том же направлении, поэтому вы можете провести одноразовый тест, когда приложение начнет проверять, в каком направлении растет стек ... Или сделайте какую-нибудь выдумку:
FooClass::FooClass() {
char dummy;
ptrdiff_t displacement = &dummy - reinterpret_cast<char*>(this);
if (displacement > 10000 || displacement < -10000) {
throw "Not on the stack - maybe..";
}
}
Я думаю, что this и dummy всегда будут рядом друг с другом, независимо от того, в куче они или в стеке
@Vargas - я не согласен. dummy всегда будет в стеке, потому что это автоматическая локальная переменная. Указатель this
может указывать либо на стек (если FooClass используется как локальная переменная), либо на кучу (если FooClass выделен в куче или агрегатируется внутри класса, который затем выделяется в куче).
Вы правы, я ошибочно принял dummy
за переменную-член ... извините
@Ник
Этого можно было избежать, создав класс, производный от Foo или агрегирующий его. Я думаю, что то, что я предлагаю (хотя и не надежное), все равно будет работать для производных и агрегирующих классов.
Например:
struct MyStruct {
Foo m_foo;
};
MyStruct* p = new MyStruct();
Здесь я создал экземпляр Foo в куче, минуя скрытый новый оператор Foo.
Вы можете объявить его как интерфейс и управлять классом реализации напрямую из собственного кода.
Ответ Ника - хорошая отправная точка, но неполная, так как вам действительно нужно перегрузить:
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% способ.
Будет ли частный конструктор в сочетании с общедоступным статическим фабричным методом (возврат по значению) выполнять то же самое?
@kevinarpe Это зависит от того, насколько буквально мы читаем вопрос. Точный код Foo myfooObject;
из вопроса не будет компилироваться, если вы это сделаете. Тем не менее, я предпочитаю подход, больше похожий на то, что вы предлагаете, если я пытаюсь контролировать создание объектов.
Обратите внимание, что это можно обойти, используя ::new
вместо new
, так как это будет выполнять поиск operator new
в глобальной области.
Поскольку заголовки отладки могут переопределить новую сигнатуру оператора, лучше всего использовать сигнатуры ... как полное средство:
private:
void* operator new(size_t, ...) = delete;
void* operator new[](size_t, ...) = delete;
этого можно избежать, сделав конструкторы закрытыми и предоставив статический член для создания объекта в стеке.
Class Foo
{
private:
Foo();
Foo(Foo& );
public:
static Foo GenerateInstance() {
Foo a ; return a;
}
}
это сделает создание объекта всегда в стеке.
Зачем вам это нужно?