Есть ли способ обеспечить, чтобы экземпляры были только в стеке?

У меня есть класс C ++, для которого я только хочу, чтобы он был создан в стеке. Я использую API для доступа к контенту, который был разработан на другом (интерпретированном) языке, который поставляется со своей собственной сборкой мусора. Механизмы в этом языке знают достаточно, чтобы оставить любой контент, на который он находит ссылки, только в стеке, и поскольку этот собственный класс содержит такую ​​ссылку, жизненно важно, чтобы для правильного поведения пользователь собственного класса C ++ это делал никогда не пытайтесь выделить экземпляр этого где-либо еще.

Обратите внимание, я не только хочу запретить экземпляру моего класса выделяться новым (если бы это было все, что мне нужно было сделать, я мог бы перегружать класс new оператор и сделать его закрытым, или явно удалить его после C ++ 11), но также запретить любые статические или возможные глобальные экземпляры класса. Единственный надежный способ создания экземпляра этого класса должен быть в стеке, и я хотел бы как-то это гарантировать. Насколько я знаю, делая new закрытое или удаленное также не препятствует тому, чтобы другой класс был объявлен с моим классом как переменная-член, и экземпляр этого был размещен в куче.

Я управляю этим прямо сейчас, чтобы слово «Local» было частью имени класса в качестве дружеского напоминания пользователю, что экземпляр предназначен только для использования в стеке, но, конечно, это не так. Это на самом деле обеспечивается компилятором или любым другим механизмом, и я бы предпочел решение, которое является более принудительным.

В идеале я хочу убедиться в этом во время компиляции и потерпеть неудачу при неправильном использовании. Если это просто невозможно, создание исключения во время выполнения при создании экземпляра все еще является приемлемым резервным вариантом. Решения, которые работают в C ++ 11 или C ++ 14, хороши.

Обратите внимание, что этот вопрос определенно НЕ такой же как этот один, который только хотел предотвратить выделение new

31

Решение

Отказ от ответственности: «стек» не является частью стандарта 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 не сможет работать; это удобно, когда исключения не разрешены.

Недостатком является то, что в настоящее время вы не можете напрямую использовать операторы потока управления внешней области видимости внутри лямбды. / * Подсказка: мы работаем над этим. * /

40

Другие решения

Хорошо, вот мое мнение:

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 в комплекте) и позвони другому не встроенный функция и использовать его в качестве фактической точки входа.

5

Возможность состоит в том, чтобы разрешить только временные переменные (с увеличенным временем жизни), что-то вроде:

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 с гарантированным разрешением копирования.

5