有没有办法强制执行只有堆栈的实例?

mar*_*964 31 c++ c++11 automatic-storage c++14

我有一个C++类,我只希望它在堆栈上实例化.我正在使用api访问以另一种(解释)语言开发的内容,该语言带有自己的垃圾收集.这种语言中的机制足以将它在堆栈中找到引用的任何内容留下来,并且由于这个本机类包含这样的引用,因此对于正确的行为来说,它本身就是C++类的用户是至关重要的.永远不要尝试在其他任何地方分配它的实例.

注意,我不仅要禁止我的类的实例被分配new(如果这是我需要做的全部,我可以重载类的new运算符并将其设为私有,或者从C++ 11开始明确删除它),但也要禁止该类的任何静态或可能的全局实例.安全地实例化这个类的唯一有效方法应该在堆栈上,我想以某种方式保证.据我所知,new私有或删除它也不会阻止另一个类被我的类声明为成员变量和在堆上分配的实例.

我现在如何处理这个问题就是将"Local"作为类名称的一部分,作为对用户的友好提醒,该实例仅用于堆栈,但当然,这不是'实际上是由编译器或任何其他机制强制执行的,我更喜欢一种更具可执行性的解决方案.

理想情况下,我想在编译时确保这一点,如果使用不正确则编译失败.如果这根本不可能,那么在构造实例时在运行时抛出异常仍然是可接受的回退.在C++ 11或C++ 14中工作的解决方案很好.

请注意,这个问题肯定是一样的一个,它只是想防止allocaton与new

lor*_*rro 40

免责声明:据我所知,'stack'不是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;
};
Run Code Online (Sandbox Code Playgroud)

用法:传递lambda采用A和AE的ctor参数

return A::cps_make([&](A a) {
   /* do something with a */
   return true;
});
Run Code Online (Sandbox Code Playgroud)

函数参数始终是ASDV.

代码如何工作:cps_make采用一个函子(通常是一个lambda),它接受给定类型的实例; 和可选的ctor参数.它创建实例(通过将任何可选参数转发给ctor),调用仿函数并返回仿函数返回的内容.由于函子可以是C++ 11中的lambda,因此它不会破坏正常的代码流.

CPS的优点是,你可以通过在C++ 14中使用auto-lambda来实现静态多态:你的cps_make()可以创建你想要的任何东西(层次结构,变体,任何等).然后,为已关闭的层次结构保存虚拟开销.如果ctor失败,你甚至可以有一个正常流量的lambda; 当异常不行时,这很方便.

缺点是,目前你不能直接使用lambda内部范围的控制流语句./*提示:我们正在努力.*/

  • @Sjoerd:1)成员:确实不可能.如果可以的话,你将能够创建一个封闭类的静态实例,这违背了OP的意图.2)我试图发布最简单的解决方案,而不是最方便的(lib-ready)解决方案.在家里,我可能会对运算符>>进行覆盖以提供lambda.3)是的,这是很难的代码.不会称其为混淆:CPS已存在一段时间了.并不适合每个人,你应该总是权衡成本与收益.编译器不检查Doc:如果错误成本很高,请使用类型,如果低,请记录它. (12认同)
  • 我可以请求downvoters解释为什么你认为这是一个错误的答案?然后我可以学习...... (10认同)
  • 缺点:1)无法使用A作为成员或基类.2)创建A的参数是在使用A的函数之后,这是一个非常奇怪的顺序.3)结果,主要的影响是它混淆了代码.恕我直言,记录"使用A不在堆栈上创建未定义的行为"更符合C++方法. (9认同)
  • 那很聪明.下来的选票可能来自那些不明白这一点的人.除了显示代码之外,它可能有助于解释. (6认同)
  • @lorro考虑使用`operator - >*`代替.就像`f - >*pass_A(args ...)`做一个`f(A(args ...))`.`operator - >*`可以自由覆盖,很少使用,并且是成员函数解引用运算符; 这有点像在`f`上编写成员函数. (2认同)
  • [here](http://coliru.stacked-crooked.com/a/5209a02490e84dd6)是一个使用CRTP来减少实例样板的实例.缺少将(大多数)事物设为私有以避免绕过应该如何使用它.如果需要,您有权将其复制到您的答案中,并进行修改. (2认同)

Fat*_*KIR 6

好的,所以这是我的看法:

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
}
Run Code Online (Sandbox Code Playgroud)

不可否认,我的解决方案并不是最干净的解决方案,但它允许您继续使用常规本地对象语法.

基本上,诀窍是在堆栈上放置2个额外的对象,而不是我们的对象:一个在线程的开头,一个在构造函数中.因此,其中一个对象是我们的对象之后的堆栈上创建的,之前是其中之一.有了这些信息,我们就可以检查这3个对象的地址顺序.如果对象真的在堆栈上,它的地址应该在中间.

但是,C++没有定义函数作用域中对象的地址顺序,因此执行如下操作:

int main()
{
    int a;
    int b;
    int c;
}
Run Code Online (Sandbox Code Playgroud)

难道不能保证&b在中间&a&c.

为了解决这个问题,我们可以保留a在main函数中,bc以不同的方式移动非内联函数:

void NO_INLINE foo()
{
    int b;
    int c;
}

int main()
{
    int a;
    foo();
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,由于编译器不知道的本地变量foomain,&a> &b,&c&a< &b,&c.通过应用同样的事情c通过其移动到另一个非inlineable功能,我们可以保证&B是在中部&a&c.

在我的实现中,stuff函数是foo函数,我们c进入的函数是函数的构造函数only_on_stack.

实际工作实施在这里:https://gist.github.com/FatihBAKIR/dd125cf4f06cbf13bb4434f79e7f1d43

它应该可以工作,无论堆栈是向下还是向上,无论目标文件类型如何,并且希望ABI,只要编译器不以某种方式重新排序非内联函数的局部变量.

这是用-O3linux上的g ++ - 6和mac os x上的最新clang 测试的.它应该适用于MSVC,希望有人可以测试它.

两者的输出是:

1: I'm NOT on stack
2: I'm on stack
3: I'm NOT on stack
4: I'm NOT on stack
Run Code Online (Sandbox Code Playgroud)

用法基本上是,你把一个stack_marker对象放在每个线程(main包含)的开头,并调用另一个不是可内联的函数,并将它用作你的实际入口点.


Jar*_*d42 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
}
Run Code Online (Sandbox Code Playgroud)

它在C++ 17中将不再有效且具有保证的副本省略.

  • 直到保证elision命中并且你可以做`new A(A :: Make())`无论如何 (5认同)