我有一个C ++类,我只希望它在堆栈上实例化。我正在使用api访问以另一种(解释)语言开发的内容,该语言带有自己的垃圾收集。这种语言中的机制足以将它在堆栈中找到引用的任何内容留下来,并且因为这个本机类包含这样的引用,所以对于正确的行为,它本身就是C ++类的用户是至关重要的。不要试图在其他任何地方分配它的实例。
注意,我不仅要禁止我的类实例被分配new(如果我需要这样做,我可以重载类new
运算符并将其设为私有,或者从C ++ 11开始明确地删除它,但同时也禁止该类的任何静态或可能的全局实例。安全地实例化这个类的唯一有效方法应该在堆栈上,我想以某种方式保证。据我所知,使new
私有或删除它也不会阻止另一个类被我的类声明为成员变量,并且在堆上分配它的实例。
我现在如何管理这个词是为了#34; Local"作为类的名称的一部分,作为对用户的友好提醒,该实例仅用于堆栈,但当然,这实际上并不是由编译器或任何其他机制强制执行的,而且我我宁愿选择一种更具可执行性的解决方案。
理想情况下,我想在编译时确保这一点,如果使用不正确则编译失败。如果这根本不可能,那么在构造实例时在运行时抛出异常仍然是可接受的回退。在C ++ 11或C ++ 14中工作的解决方案很好。
请注意,这个问题肯定是 NOT 和this一样,只是想阻止分配new
答案 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函数中,并将b
和c
移到另一个强制非内联函数中:
void NO_INLINE foo()
{
int b;
int c;
}
int main()
{
int a;
foo();
}
在这种情况下,由于编译器无法知道foo
中main
的{{1}}的局部变量,&a
&gt; &b
,&c
或&a
&lt; &b
&c
,c
。通过将&a
移动到另一个非可内联函数,将相同的内容应用于&c
,我们可以保证&amp; b位于stuff
和foo
的中间位置。
在我的实现中,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中,它将不再有效。