是否将一个C ++对象传递到自己的构造函数中? [英] Is passing a C++ object into its own constructor legal?

查看:143
本文介绍了是否将一个C ++对象传递到自己的构造函数中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我惊讶地意外地发现以下作品:

  #include< iostream> 
int main(int argc,char ** argv)
{
struct Foo {
Foo(Foo& bar){
std :: cout< & bar<< std :: endl;
}
};
Foo foo(foo); //我不能相信这个工作...
std :: cout<< & foo<< std :: endl; // but it does ...
}

对象转换成自己的构造函数。这看起来像是源级别的循环定义。标准是否真的允许你在对象被构造之前将对象传递到函数中,或者是这个未定义的行为?



我想这并不奇怪,因为所有的类成员函数已经有一个指向他们的类实例的数据作为一个隐式参数。并且数据成员的布局在编译时是固定的。



注意,我不是问这是有用的还是一个好主意;

解决方案

这不是未定义的行为,虽然 foo 是未初始化的,你正在使用它是标准允许的方式。为一个对象分配空间之后,在完全初始化之前,您可以使用有限的方式。



这是由缺陷报告363:从自我初始化,说:



< >

如果是这样,UDT的自初始化的语义是什么?
例如

  #include< stdio.h& 

struct A {
A(){printf(A :: A()%p\\\
,this); }
A(const A& a){printf(A :: A(const A&)%p%p \\\
,this,& a); }
〜A(){printf(A ::〜A()%p\\\
,this); }
};

int main()
{
A a = a;
}

可以编译并打印:

  A :: A(const A&)0253FDD8 0253FDD8 
A ::〜A()0253FDD8
pre>

且解析度为:


3.8 [basic.life]第6段表示此处的引用有效。它允许在完全初始化之前获取类对象的地址,并且允许将它作为参数传递给引用参数,只要引用可以直接绑定。除了无法为printf中的%p投放指向void *的指针之外,这些示例都是符合标准的。


草案C ++ 14标准的 3.8 [basic.life] 全部引用如下:


类似地,在对象的生命周期开始之前,但在对象将占用的
存储已经被分配之后,或者在
生存期之后对象已经结束,并且在
对象占用的存储之前,重用或释放,可以使用引用
原始对象的任何glvalue,但只能以有限的方式使用。对于正在构建或销毁的对象
,请参见12.7。否则,这样的glvalue
引用分配的存储(3.7.4.2),并且使用不依赖于其值的
glvalue的属性是明确定义的。程序
在以下情况下具有未定义的行为:




  • 左值到右值转换glvalue,


  • glvalue用于访问非静态数据成员或调用
    对象的非静态成员函数,


  • glvalue绑定到虚拟基类(8.5.3)或

    的引用

  • glvalue用作dynamic_cast(5.2.7)或作为typeid的操作数的操作数。





我们不会对 foo 做任何处理,因为它们属于未定义的行为,如上面的项目符号所定义。



如果我们用clang尝试这个,我们会看到一个不祥的警告( 查看实时 ):


警告:变量'foo'在其自己的初始化[-Wuninitialized]


这是一个有效的警告,因为从未初始化的自动变量产生和不确定的值是未定义的行为,但在这种情况下,您只是绑定一个引用,并在构造函数中获取变量的地址,不产生不确定的值并且有效。另一方面,遵循草案C ++ 11标准的自初始化示例

  int x = x; 

会调用未定义的行为。



a href =http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#453 =nofollow>活动问题453:引用只能绑定到有效对象也似乎相关,但仍然是开放的。初始建议的语言与缺陷报告363一致。


I am surprised to accidentally discover that the following works:

#include <iostream>            
int main(int argc, char** argv)
{
  struct Foo {
    Foo(Foo& bar) {
      std::cout << &bar << std::endl;
    }
  };
  Foo foo(foo); // I can't believe this works...
  std::cout << &foo << std::endl; // but it does...
}

I am passing the address of the constructed object into its own constructor. This looks like a circular definition at the source level. Do the standards really allow you to pass an object into a function before the object is even constructed or is this undefined behavior?

I suppose it's not that odd given that all class member functions already have a pointer to the data for their class instance as an implicit parameter. And the layout of the data members is fixed at compile time.

Note, I'm NOT asking if this is useful or a good idea; I'm just tinkering around to learn more about classes.

解决方案

This is not undefined behavior, although foo is uninitialzed you are using it a way that is allowed by the standard. After space is allocated for an object but before it is fully initialized you are allowed to use it limited ways. Both binding a reference to that variable and taking its address is allowed.

This is covered by defect report 363: Initialization of class from self which says:

And if so, what is the semantics of the self-initialization of UDT? For example

 #include <stdio.h>

 struct A {
        A()           { printf("A::A() %p\n",            this);     }
        A(const A& a) { printf("A::A(const A&) %p %p\n", this, &a); }
        ~A()          { printf("A::~A() %p\n",           this);     }
 };

 int main()
 {
  A a=a;
 }

can be compiled and prints:

A::A(const A&) 0253FDD8 0253FDD8
A::~A() 0253FDD8

and the resolution was:

3.8 [basic.life] paragraph 6 indicates that the references here are valid. It's permitted to take the address of a class object before it is fully initialized, and it's permitted to pass it as an argument to a reference parameter as long as the reference can bind directly. Except for the failure to cast the pointers to void * for the %p in the printfs, these examples are standard-conforming.

The full quote of section 3.8 [basic.life] from the draft C++14 standard is as follows:

Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways. For an object under construction or destruction, see 12.7. Otherwise, such a glvalue refers to allocated storage (3.7.4.2), and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:

  • an lvalue-to-rvalue conversion (4.1) is applied to such a glvalue,

  • the glvalue is used to access a non-static data member or call a non-static member function of the object, or

  • the glvalue is bound to a reference to a virtual base class (8.5.3), or

  • the glvalue is used as the operand of a dynamic_cast (5.2.7) or as the operand of typeid.

We are not doing anything with foo that falls under undefined behavior as defined by the bullets above.

If we try this with clang we see an ominous warning (see it live):

warning: variable 'foo' is uninitialized when used within its own initialization [-Wuninitialized]

It is a valid warning since producing and indeterminate value from an uninitialized automatic variable is undefined behavior but in this case you are just binding a reference and taking the address of the variable within the constructor which does not produce an indeterminate value and is valid. On the other hand the following self-initialization example from the draft C++11 standard:

int x = x ;

does invoke undefined behavior.

Active issue 453: References may only bind to "valid" objects also seems relevant but is still open. The initial proposed language is consistent with defect report 363.

这篇关于是否将一个C ++对象传递到自己的构造函数中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆