C++ 在对象自身的构造中使用对象有什么问题吗?
我正在存储一个需要对对象进行操作的操作,但我不想使用继承。因此,我使用的技术是让一个非成员函数接受指向对象的指针,然后将其存储在对象中,如下所示:C++ 在对象自身的构造中使用对象有什么问题吗?,c++,c++11,C++,C++11,我正在存储一个需要对对象进行操作的操作,但我不想使用继承。因此,我使用的技术是让一个非成员函数接受指向对象的指针,然后将其存储在对象中,如下所示: struct command { command() { } command(const std::function<void()>& action) : action(action) { } int n; std::function<voi
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;
}
struct命令
{
命令()
{
}
命令(const std::function&action)
:行动
{
}
int n;
功能动作;
};
无效测试动作(命令*此ptr)
{
该ptr->n=5;
}
int main()
{
命令com(std::bind(test_action,&com));
com.action();
std::cout首先:什么是对象
[介绍对象]\1
[…]对象是一个
储存区域[……]
在对象的生存期开始之前分配存储:
[基本生活]
在对象的生存期开始之前但在存储之后
对象将占用的对象已分配[…]任何
指对象将位于或曾经位于的存储位置
可以使用,但只能以有限的方式使用。对于正在建造或破坏的对象,请参见12.7[建造和破坏]。否则,
此类指针指的是已分配的存储(3.7.4.2),并使用指针,就像指针为void*类型一样,
定义明确
因此,指针指的是分配的空间,使用它没有坏处。您只需要请求堆栈地址,任何编译器都应该能够正确地计算出来。在这个特定实例中,不需要对象本身的初始化操作
这是有意义的,因为在一个经典的AST编译器中,如果您想查看声明器的标准层次结构,可以使用一个简单的玩具代码,如
class command {
public:
command(int) {
}
};
int funct(command*) {
return 2;
}
int main() {
command com(funct(&com));
}
线路
command com(funct(&com));
其解释如下:
[dcl.decl]
最后,对于您的代码,这是gcc编译这一行(-O0)的方式
命令com(std::bind(test_action,&com));
->
movq%rax,-104(%rbp)
leaq-104(%rbp),%rdx
leaq-96(%rbp),%rcx
movl测试动作(命令*),%esi
movq%rcx,%rdi
movq%rax,-136(%rbp)#8字节溢出
movq%rcx,-144(%rbp)#8字节溢出
调用ZSt4bindIRFvP7commandEJS1\u-EENSt12\u-Bind\u-helperIT\u-JDpT0\u-EE4typeEOS5\u-DpOS6_
leaq-80(%rbp),%rax
movq%rax,%rdi
movq-144(%rbp),%rsi#8字节重新加载
movq%rax,-152(%rbp)#8字节溢出
调用函数IfVveeC1 ist5\u BindIfPfVp7命令5\u Eeet\u NST9启用\u IfxNTSR11是完整的\u Ee5值1\u 8\u可使用请参见4类型
movq-136(%rbp),%rdi#8字节重新加载
movq-152(%rbp),%rsi#8字节重新加载
callq command::command(std::function const&)
也就是说:在调用构造函数之前,只需要从基指针传递到绑定函数的一组堆栈地址
如果您在构建对象之前实际尝试使用该对象,情况会有所不同(使用虚拟函数表可能会变得棘手)
旁注:如果复制对象或按值传递对象并超出范围(并且仍然保留堆栈位置的地址),则不能保证安全。另外:如果编译器决定存储它(无论出于何种架构/原因)作为与基本帧的偏移量,您可能会偏离到未定义的behaviorland。是的,一旦对象被声明(以及分配的存储空间)但尚未初始化,您就可以使用指向该对象的指针。访问对象本身[以非常受限的方式除外]提供未定义的行为;但您不能这样做。C++11 3.8/5对此进行了描述:
在对象的生存期开始之前,但在对象将占用的存储被分配之后[…]可以使用指向对象将要或曾经所在的存储位置的任何指针,但只能以有限的方式使用。[…]使用指针就像指针是void*
类型一样,是定义良好的
您只需将其传递给bind
,它将指针值复制到绑定函数包装器中,该包装器将其视为使用它,就好像它是void*
一样,只要您在构建对象之前不实际处理它,您就是黄金。正如@MarcoA所评论的,您只使用了在此之前的地址。这是正确的如果你想经常这样做的话,保护复制和移动是个好主意。+1,尽管与偶然事件争论是完全错误的好方法。我同意,而且我通常不会从“它编译”或“它工作”中得出任何观察结果.未定义的行为是一个棘手的问题。无论如何,我认为在这种情况下没有任何伤害。如果我遗漏了什么,请告诉我,我会编辑post@OliverCharlesworth只是添加了一些细节,我的意思是,汇编程序所证明的是,这个特定的编译器生成了“安全的”此特定实例中的代码。这对是否为UB的问题毫无帮助。需要的是引用标准(或等效标准),说明&com
在此场景中是有效指针。无耻插头:
simple-declaration:
attribute-specifier-seqopt decl-specifier-seqopt init-declarator-listopt;
...
initializer:
brace-or-equal-initializer
( expression-list ) // The declaration statement is already specified
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&)