Подтвердить что ты не робот

Есть ли способ принудительно использовать эти экземпляры только в стеке?

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

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

Как я сейчас управляю этим, это иметь слово "Локальный" как часть имени класса как дружественное напоминание пользователю о том, что экземпляр предназначен только для использования в стеке, но, разумеется, это на самом деле не выполняется компилятором или каким-либо другим механизмом, и я предпочел бы более эффективное решение.

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

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

4b9b3361

Ответ 1

Отказ от ответственности: "стек" не является частью стандартного С++, я знаю, что у нас есть ASDV (переменные продолжительности автоматического хранения). ABI может определять стек. Обратите внимание, что иногда они передаются в регистры, которые, я считаю, в порядке.

Определить стиль CPS (стиль продолжения) factory:

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;
};

Использование: передать лямбда, взяв A и параметры ctor A. Например:

return A::cps_make([&](A a) {
   /* do something with a */
   return true;
});

Аргументы функции всегда являются ASDV внутри.

Как работает код: cps_make принимает функтор (обычно лямбда), который принимает экземпляр данного типа; и необязательные параметры ctor. Он создает экземпляр (путем перенаправления любых необязательных параметров в ctor), вызывает функтор и возвращает возвращаемый функтор. Поскольку функтор может быть лямбдой в С++ 11, он не нарушает нормальный поток кода.

Красота CPS заключается в том, что вы можете иметь статический полиморфизм, используя авто-лямбда в С++ 14: ваш cps_make() может создавать практически все, что вы пожелаете (иерархия, вариант, любой и т.д.). Затем вы сохраняете виртуальные накладные расходы для закрытых иерархий. У вас может даже быть лямбда для нормального потока, а один, если ctor потерпит неудачу; это удобно, когда исключения не идут.

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

Ответ 2

Хорошо, так вот мой прием:

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 дополнительных объекта, кроме нашего объекта: один в начале потока и один в конструкторе. Поэтому один из объектов создается в стеке после нашего объекта и одного из них раньше. С помощью этой информации мы могли бы просто проверить порядок адресов этих трех объектов. Если объект действительно находится в стеке, его адрес должен быть посередине.

Однако С++ не определяет адресный порядок объектов в области функций, поэтому делает что-то вроде этого:

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

Ответ 3

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

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
}

Он больше не будет действителен в С++ 17 с гарантированным копированием.