У меня есть класс C ++, для которого я только хочу, чтобы он был создан в стеке. Я использую API для доступа к контенту, который был разработан на другом (интерпретированном) языке, который поставляется со своей собственной сборкой мусора. Механизмы в этом языке знают достаточно, чтобы оставить любой контент, на который он находит ссылки, только в стеке, и поскольку этот собственный класс содержит такую ссылку, жизненно важно, чтобы для правильного поведения пользователь собственного класса C ++ это делал никогда не пытайтесь выделить экземпляр этого где-либо еще.
Обратите внимание, я не только хочу запретить экземпляру моего класса выделяться новым (если бы это было все, что мне нужно было сделать, я мог бы перегружать класс new
оператор и сделать его закрытым, или явно удалить его после C ++ 11), но также запретить любые статические или возможные глобальные экземпляры класса. Единственный надежный способ создания экземпляра этого класса должен быть в стеке, и я хотел бы как-то это гарантировать. Насколько я знаю, делая new
закрытое или удаленное также не препятствует тому, чтобы другой класс был объявлен с моим классом как переменная-член, и экземпляр этого был размещен в куче.
Я управляю этим прямо сейчас, чтобы слово «Local» было частью имени класса в качестве дружеского напоминания пользователю, что экземпляр предназначен только для использования в стеке, но, конечно, это не так. Это на самом деле обеспечивается компилятором или любым другим механизмом, и я бы предпочел решение, которое является более принудительным.
В идеале я хочу убедиться в этом во время компиляции и потерпеть неудачу при неправильном использовании. Если это просто невозможно, создание исключения во время выполнения при создании экземпляра все еще является приемлемым резервным вариантом. Решения, которые работают в C ++ 11 или C ++ 14, хороши.
Обратите внимание, что этот вопрос определенно НЕ такой же как этот один, который только хотел предотвратить выделение new
Отказ от ответственности: «стек» не является частью стандарта C ++, насколько я знаю, там у нас есть ASDV (переменные продолжительности автоматического хранения). ABI может определить стек. Обратите внимание, что иногда они передаются в регистрах, что, я считаю, нормально в вашем случае.
Определите метод фабрики CPS (стиль продолжения):
class A {
public:
template<typename F, typename... Args>
static auto cps_make(F f, Args&&... args) {
return f(A(std::forward<Args>(args)...));
}
private:
A(/* ... */) {}
A(const A&) = delete;
A(A&&) = delete;
};
Использование: пропуск лямбда-взятия А и параметров ctor А. Э.
return A::cps_make([&](A a) {
/* do something with a */
return true;
});
Аргументы функции всегда внутри ASDV.
Как работает код: cps_make использует функтор (обычно лямбда), который принимает экземпляр данного типа; и необязательные параметры ctor. Он создает экземпляр (перенаправляя любые необязательные параметры в ctor), вызывает функтор и возвращает то, что возвращает функтор. Поскольку функтор может быть лямбда-выражением в C ++ 11, он не нарушает нормальный поток кода.
Прелесть CPS в том, что вы можете иметь статический полиморфизм, просто используя автоматическую лямбду в C ++ 14: ваш cps_make () может создавать практически все, что вы пожелаете (иерархия, вариант, любой и т. Д.). Затем вы сохраняете виртуальные издержки для закрытых иерархий. Вы можете даже иметь лямбду для нормального потока и один, если ctor не сможет работать; это удобно, когда исключения не разрешены.
Недостатком является то, что в настоящее время вы не можете напрямую использовать операторы потока управления внешней области видимости внутри лямбды. / * Подсказка: мы работаем над этим. * /
Хорошо, вот мое мнение:
struct stack_marker
{
thread_local static uint8_t* marker;
uint8_t stk;
stack_marker()
{
if (marker != nullptr)
{
throw std::runtime_error("second twice marker! don't do it");
}
marker = &stk;
}
};
thread_local uint8_t* stack_marker::marker = nullptr;
void sort3(uint8_t* (&a)[3]); //sorts 3 pointers, see gist
class only_on_stack
{
uint8_t place;
public:
NO_INLINE only_on_stack(int x)
{
uint8_t a;
if (!stack_marker::marker)
{
// not initialized yet, either forgot to put stack_marker in main
// or we are running before main, which is static storage
//throw std::runtime_error("only on stack object created in non-stack");
std::cout << x << ": I'm NOT on stack\n";
return;
}
uint8_t* ptrs[] = {
stack_marker::marker,
&place,
&a
};
sort3(ptrs);
if (ptrs[1] == &place) // place must be in the middle
{
std::cout << x << ": I'm on stack\n";
}
else
{
//throw std::runtime_error("only_on_stack object created in non-stack");
std::cout << x << ": I'm NOT on stack\n";
}
}
};
only_on_stack static_storage(1);
thread_local only_on_stack tl_storage(4);
int NO_INLINE stuff()
{
only_on_stack oos(2);
}
int main()
{
stack_marker mrk;
stuff();
auto test = new only_on_stack(3);
tl_storage; // access thread local to construct, or gcc omits it
}
По общему признанию, мое решение не является самым чистым из них, но оно позволяет вам продолжать использовать обычный синтаксис локальных объектов.
По сути, хитрость заключается в том, чтобы поместить в стек 2 дополнительных объекта, отличных от нашего объекта: один в начале потока и один в конструкторе. Поэтому один из объектов создается в стеке после наш объект и один из них до. С помощью этой информации мы могли бы просто проверить порядок адресов этих 3 объектов. Если объект действительно находится в стеке, его адрес должен быть посередине.
Однако C ++ не определяет порядок адресов объектов в области действия функции, поэтому делает что-то вроде этого:
int main()
{
int a;
int b;
int c;
}
Есть ли не гарантировать, что &b
находится в середине &a
а также &c
,
Чтобы обойти это, мы могли бы сохранить a
в основной функции и двигаться b
а также c
в другом сила не встроенная функция:
void NO_INLINE foo()
{
int b;
int c;
}
int main()
{
int a;
foo();
}
В этом случае, поскольку компилятор не может знать локальные переменные foo
в main
, &a
> &b
, &c
или же &a
< &b
, &c
, Применяя то же самое к c
переместив его в другую не встроенную функцию, мы могли бы гарантировать, что &б находится в середине &a
а также &c
,
В моей реализации, stuff
функция является foo
функция и функция, которую мы перемещаем c
в это конструктор only_on_stack
,
Фактическая рабочая реализация здесь: https://gist.github.com/FatihBAKIR/dd125cf4f06cbf13bb4434f79e7f1d43
Он должен работать независимо от того, растет ли стек вниз или вверх, независимо от типа объектного файла и, возможно, от ABI, если компилятор не каким-либо образом переупорядочивает локальные переменные не встроенных функций.
Это было проверено с -O3
на g ++ — 6 на linux и последний clang на mac os x. Это должен работать на MSVC, надеюсь, кто-то может это проверить.
Выход из обоих:
1: I'm NOT on stack
2: I'm on stack
3: I'm NOT on stack
4: I'm NOT on stack
Использование в основном, вы положили stack_marker
объект в начале каждого потока (main
в комплекте) и позвони другому не встроенный функция и использовать его в качестве фактической точки входа.
Возможность состоит в том, чтобы разрешить только временные переменные (с увеличенным временем жизни), что-то вроде:
class A
{
private:
A() = default;
A(const A&) = delete;
A(A&&) = delete;
A& operator =(const A&) = delete;
A& operator =(A&&) = delete;
public:
static A Make() { return {}; }
};
auto&& g = A::Make(); // possible :/
int main() {
auto&& a = A::Make(); // possible
#if 0
new A(); // error
struct InnerA
{
A a; // error
};
#endif
}
Он больше не будет действительным в C ++ 17 с гарантированным разрешением копирования.