我存储了需要对某个对象进行操作但我不想使用继承的操作。所以我使用的技术是使用非成员函数接受指向对象的指针,然后将其存储在对象中,如下所示:
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;
}
我的问题是command com(std::bind(test_action, &com));
安全吗?还是未定义的行为?
答案 0 :(得分:7)
首先关闭:什么是对象?
[intro.object] \ 1
[...]一个对象是一个 存储区域[...]
在对象的生命周期开始之前分配存储:
[basic.life]
在对象的生命周期开始之前但在存储之后 对象将占用的是[..]任何指针 指对象所在或存在的存储位置 可以使用但仅限于有限的方式。对于正在建造或销毁的物体,见12.7 [构造和破坏]。除此以外, 这样的指针指的是已分配的存储(3.7.4.2),并且使用指针就像指针的类型为void *一样, 是明确的。
因此指针指的是分配的空间,使用它没有任何害处。您只需要一个堆栈地址,任何编译器都应该能够正确识别它。在此特定实例中,不需要对象本身的初始化操作。
这是有道理的,因为在经典的AST-fashion编译器中,如果你看一下声明符的标准层次结构,用一个简单的玩具代码,比如
class command {
public:
command(int) {
}
};
int funct(command*) {
return 2;
}
int main() {
command com(funct(&com));
}
行
command com(funct(&com));
解释如下:
[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
最后,对于你的代码,这就是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&)
它是:只是来自基指针的一堆堆栈地址,它们在调用构造函数之前传递给绑定函数。
如果您在构造之前尝试使用该对象(虚拟函数表可能会变得棘手),情况会有所不同。
旁注:如果您正在复制对象并超出范围(并且仍然保留地址到堆栈位置),那么 NOT 保证是安全的)。另外:如果编译器决定将其存储(对于任何架构/原因)作为基础框架的偏移量,您可能会关闭到未定义的行为区域。
答案 1 :(得分:3)
是的,您可以在声明对象后使用指向对象的指针(并且已分配存储空间),但尚未初始化。访问对象本身[除了非常有限的方式]给出了未定义的行为;但你不能这样做。这由C ++ 11 3.8 / 5描述:
在对象的生命周期开始之前但在对象将占用的存储空间被分配之后,任何指向对象所在或将被定位的存储位置的指针都可以被使用但仅限于有限的方式。 [...]使用指针就像指针类型为
void*
一样,是明确定义的。
您只需将其传递给bind
,它会将指针值复制到绑定的函数包装器中,该函数包装器将其视为void*
使用它。