我正在存储需要对对象进行操作的操作,但我不想使用继承.所以我使用的技术是拥有一个非成员函数,它接受一个指向对象的指针,然后将它存储在对象中,如下所示:
struct command
{
command()
{
}
command(const std::function<void()>& action)
: action(action)
{
}
int n;
std::function<void()> action;
};
void test_action(command* this_ptr)
{
this_ptr->n = 5;
}
int main()
{
command com(std::bind(test_action, &com));
com.action();
std::cout << com.n;
}
Run Code Online (Sandbox Code Playgroud)
我的问题是安全command com(std::bind(test_action, &com));吗?还是未定义的行为?
首先:什么是对象?
[intro.object]\1
[...]一个物体是一个存储区域[...]
在对象的生命周期开始之前分配存储:
[basic.life]
在对象的生命周期开始之前但在对象将占用的存储被分配之后[...],可以使用任何指向对象所在或将被定位的存储位置的指针,但仅限于有限的方式.对于正在建造或销毁的物体,见12.7 [构造和破坏].否则,这样的指针指的是已分配的存储(3.7.4.2),并且使用指针就像指针的类型为void*一样,是明确定义的.
因此指针指的是分配的空间,使用它没有任何害处.您只需要一个堆栈地址,任何编译器都应该能够正确识别它.在此特定实例中,不需要对象本身的初始化操作.
这是有道理的,因为在经典的AST时尚编译器中,如果你看一下声明符的标准层次结构,就像一个简单的玩具代码一样
class command {
public:
command(int) {
}
};
int funct(command*) {
return 2;
}
int main() {
command com(funct(&com));
}
Run Code Online (Sandbox Code Playgroud)
这条线
command com(funct(&com));
Run Code Online (Sandbox Code Playgroud)
解释如下:
[dcl.decl]
simple-declaration:
attribute-specifier-seqopt decl-specifier-seqopt init-declarator-listopt;
...
initializer:
brace-or-equal-initializer
( expression-list ) // The declaration statement is already specified
Run Code Online (Sandbox Code Playgroud)
最后,对于你的代码,这是gcc如何编译这一行(-O0)
command com(std::bind(test_action, &com));
->
movq %rax, -104(%rbp)
leaq -104(%rbp), %rdx
leaq -96(%rbp), %rcx
movl test_action(command*), %esi
movq %rcx, %rdi
movq %rax, -136(%rbp) # 8-byte Spill
movq %rcx, -144(%rbp) # 8-byte Spill
callq _ZSt4bindIRFvP7commandEJS1_EENSt12_Bind_helperIT_JDpT0_EE4typeEOS5_DpOS6_
leaq -80(%rbp), %rax
movq %rax, %rdi
movq -144(%rbp), %rsi # 8-byte Reload
movq %rax, -152(%rbp) # 8-byte Spill
callq _ZNSt8functionIFvvEEC1ISt5_BindIFPFvP7commandES5_EEEET_NSt9enable_ifIXntsr11is_integralISA_EE5valueENS1_8_UselessEE4typeE
movq -136(%rbp), %rdi # 8-byte Reload
movq -152(%rbp), %rsi # 8-byte Reload
callq command::command(std::function<void ()> const&)
Run Code Online (Sandbox Code Playgroud)
它是:只是来自基指针的一堆堆栈地址,它们在调用构造函数之前传递给绑定函数.
如果您在构造之前尝试使用该对象,那么事情会有所不同(虚函数表可能会变得棘手).
旁注:如果您正在复制或通过值传递对象并超出范围(并且仍然将地址保留到堆栈位置),则不保证这是安全的.另外:如果编译器决定将其存储(对于任何架构/原因)作为基础框架的偏移量,您可能会关闭到未定义的行为区域.
| 归档时间: |
|
| 查看次数: |
577 次 |
| 最近记录: |