有没有办法强制实例只能在堆栈中使用?

时间:2017-04-19 22:04:00

标签: c++ c++11 c++14 automatic-storage

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

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

我现在如何管理这个词是为了#34; Local"作为类的名称的一部分,作为对用户的友好提醒,该实例仅用于堆栈,但当然,这实际上并不是由编译器或任何其他机制强制执行的,而且我我宁愿选择一种更具可执行性的解决方案。

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

请注意,这个问题肯定是 NOT this一样,只是想阻止分配new

3 个答案:

答案 0 :(得分: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;
};

用法:传递lambda采用A和ctor参数A.例如

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

函数参数始终是ASDV。

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

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

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

答案 1 :(得分: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
}

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

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

但是,C ++没有定义函数作用域中对象的地址顺序,因此做这样的事情:

int main()
{
    int a;
    int b;
    int c;
}

是否保证&b位于&a&c的中间位置。

要解决此问题,我们可以将a保留在main函数中,并将bc移到另一个强制非内联函数中:

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

int main()
{
    int a;
    foo();
}

在这种情况下,由于编译器无法知道foomain的{​​{1}}的局部变量,&a&gt; &b&c&a&lt; &b &cc。通过将&a移动到另一个非可内联函数,将相同的内容应用于&c,我们可以保证&amp; b位于stufffoo的中间位置。

在我的实现中,c函数是only_on_stack函数,我们将-O3移动到的函数是1: I'm NOT on stack 2: I'm on stack 3: I'm NOT on stack 4: I'm NOT on stack 的构造函数。

实际工作实施在此处:https://gist.github.com/FatihBAKIR/dd125cf4f06cbf13bb4434f79e7f1d43

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

使用stack_marker在linux上的g ++ - 6和mac os x上的最新clang进行了测试。 在MSVC上工作,希望有人可以测试它。

两者的输出是:

main

基本上,用法是在每个线程的开头放置一个SELECT EmployeeID, DriverLicenseExpiryDate AS ExpiryDate, 'Driver License' AS LicenseType FROM YourTable WHERE DriverLicenseExpiryDate BETWEEN GETDATE() AND GETDATE() + 30 UNION SELECT EmployeeID, AutoInsuranceExpiryDate AS ExpiryDate, 'Auto Insurance' AS LicenseType FROM YourTable WHERE AutoInsuranceExpiryDate BETWEEN GETDATE() AND GETDATE() + 30 UNION SELECT EmployeeID, TWICCardExpiryDate AS ExpiryDate, 'TWIC Card' AS LicenseType FROM YourTable WHERE TWICCardExpiryDate BETWEEN GETDATE() AND GETDATE() + 30 对象(包括DECLARE @startDate AS DATE = GETDATE(); DECLARE @endDate AS DATE = GETDATE() + 10; SELECT EmployeeID, DriverLicenseExpiryDate AS ExpiryDate, 'Driver License' AS LicenseType FROM YourTable WHERE DriverLicenseExpiryDate BETWEEN @startDate AND @endDate UNION SELECT EmployeeID, AutoInsuranceExpiryDate AS ExpiryDate, 'Auto Insurance' AS LicenseType FROM YourTable WHERE AutoInsuranceExpiryDate BETWEEN @startDate AND @endDate UNION SELECT EmployeeID, TWICCardExpiryDate AS ExpiryDate, 'TWIC Card' AS LicenseType FROM YourTable WHERE TWICCardExpiryDate BETWEEN @startDate AND @endDate; )并调用另一个不可内联的函数并将其用作实际条目点。

答案 2 :(得分: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中,它将不再有效。