最近,我发现了一个有趣的讨论,讨论如何允许对私有成员进行只读访问,而不会使用多个getter模糊设计,其中一个建议就是这样做:
#include <iostream>
class A {
public:
A() : _ro_val(_val) {}
void doSomething(int some_val) {
_val = 10*some_val;
}
const int& _ro_val;
private:
int _val;
};
int main() {
A a_instance;
std::cout << a_instance._ro_val << std::endl;
a_instance.doSomething(13);
std::cout << a_instance._ro_val << std::endl;
}
输出:
$ ./a.out
0
130
GotW#66明确指出对象的生命周期开始
当构造函数成功完成并正常返回时。也就是说,控制到达构造函数体的末尾或更早的return语句。
如果是这样,我们无法保证在执行_val
时正确创建_ro_val(_val)
memeber。那么上面的代码怎么工作呢?是不确定的行为?或者原始类型是否为对象的生命周期赋予了一些异常?
有人能指出一些可以解释这些事情的参考资料吗?
答案 0 :(得分:5)
在调用构造函数之前,如果在本地存储上创建对象,则为Freestore上的对象(如果使用new
)或堆栈保留适当的内存量。这意味着_val
的内存已经在成员初始化列表中引用时分配,只是该内存尚未正确初始化。
_ro_val(_val)
使引用成员_ro_val
引用为_val
分配的内存,这可能实际上包含此时的任何内容。
您的程序中仍然存在未定义的行为,因为您应该在构造函数体/成员初始化列表中将_val
显式初始化为0
(或您选择的某个值)。输出{{ 1}}在这种情况下只是因为你很幸运它可能会给你一些其他值,因为0
未被初始化。查看演示UB的gcc 4.3.4上的行为 here 。
但至于问题,是的,行为确实定义明确。
答案 1 :(得分:1)
对象的地址不会改变。
即。它的定义很明确。
然而,所示技术只是过早优化。你不节省程序员的时间。使用现代编译器,您不会节省执行时间或机器代码大小。但是你确实使这些对象不可分配。
干杯&amp;第h。,
答案 2 :(得分:0)
在我看来,使用未初始化的对象初始化引用是合法的(明确定义的)。这是合法但标准的(好的,最新的C ++ 11草案,第8.5.3.3段)建议使用有效的(完全构造的)对象作为初始化器:
A reference shall be initialized to refer to a valid object or function.
同一段落中的下一句话会在创建参考时引发更多亮点:
[Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by dereferencing a null pointer, which causes undefined behavior.]
我理解引用创建意味着绑定对通过解除引用其指针获得的对象的引用,这可能解释了初始化类型T&amp;类型的引用的最小先决条件。具有为类型T的对象保留的存储器部分的地址(保留但尚未初始化)。
通过引用访问未初始化的对象可能很危险。
我编写了一个简单的测试应用程序,演示了使用未初始化对象的引用初始化以及通过它访问该对象的后果:
class C
{
public:
int _n;
C() : _n(123)
{
std::cout << "C::C(): _n = " << _n << " ...and blowing up now!" << std::endl;
throw 1;
}
};
class B
{
public:
// pC1- address of the reference is the address of the object it refers
// pC2- address of the object
B(const C* pC1, const C* pC2)
{
std::cout << "B::B(): &_ro_c = " << pC1 << "\n\t&_c = " << pC2 << "\n\t&_ro_c->_n = " << pC1->_n << "\n\t&_c->_n = " << pC2->_n << std::endl;
}
};
class A
{
const C& _ro_c;
B _b;
C _c;
public:
// Initializer list: members are initialized in the order how they are
// declared in class
//
// Initializes reference to _c
//
// Fully constructs object _b; its c-tor accesses uninitialized object
// _c through its reference and its pointer (valid but dangerous!)
//
// construction of _c fails!
A() : _ro_c(_c), _b(&_ro_c, &_c), _c()
{
// never executed
std::cout << "A::A()" << std::endl;
}
};
int main()
{
try
{
A a;
}
catch(...)
{
std::cout << "Failed to create object of type A" << std::endl;
}
return 0;
}
输出:
B::B(): &_ro_c = 001EFD70
&_c = 001EFD70
&_ro_c->_n = -858993460
&_c->_n = -858993460
C::C(): _n = 123 ...and blowing up now!
Failed to create object of type A