建筑结构&
我在弄清楚const在特定情况下如何应用时遇到了一些麻烦。这是我的代码:
struct Widget { Widget():x(0), y(0), z(0){} int x, y, z; }; struct WidgetHolder //Just a simple struct to hold four Widgets. { WidgetHolder(Widget a, Widget b, Widget c, Widget d): A(a), B(b), C(c), D(d){} Widget& A; Widget& B; Widget& C; Widget& D; }; class Test //This class uses four widgets internally, and must provide access to them externally. { public: const WidgetHolder AccessWidgets() const { //This should return our four widgets, but I don't want anyone messing with them. return WidgetHolder(A, B, C, D); } WidgetHolder AccessWidgets() { //This should return our four widgets, I don't care if they get changed. return WidgetHolder(A, B, C, D); } private: Widget A, B, C, D; }; int main() { const Test unchangeable; unchangeable.AccessWidgets().A.x = 1; //Why does this compile, shouldn't the Widget& be const? }
基本上,我有一类叫做测试。它在内部使用了四个小部件,我需要它来返回它们,但是如果test被声明为const,那么我也希望小部件也返回const。
有人可以向我解释为什么main()中的代码可以编译吗?
非常感谢你。
解决方案
回答
unchangeable.AccessWidgets():
此时,我们将创建一个类型为WidgetHolder的新对象。
该对象不受const保护。
我们还在WidgetHolder中创建新的小部件,而不是对Wdiget的引用。
回答
WidgetHolder
将保存无效的引用(指针)。我们正在将堆栈上的对象传递给构造函数,然后保留对其(临时)地址的引用。保证可以打破。
我们只能将引用分配给与引用本身具有相同(或者更长)生存期的对象。
如果必须保留引用,则将引用传递给构造函数。更好的是,根本不保留引用,而只是制作副本。
回答
之所以进行编译,是因为尽管WidgetHolder是const对象,但这种恒定性不会自动应用于指向WidgetHolder(由WidgetHolder指向)的对象。如果将WidgetHolder对象本身保存在只读内存中,则可以在机器级别上考虑它,我们仍然可以写入WidgetHolder指向的内容。
问题似乎在于此行:
WidgetHolder(Widget a, Widget b, Widget c, Widget d): A(a), B(b), C(c), D(d){}
正如Frank所提到的,在构造函数返回之后,WidgetHolder类中的引用将保存无效的引用。因此,我们应该将其更改为:
WidgetHolder(Widget &a, Widget &b, Widget &c, Widget &d): A(a), B(b), C(c), D(d){}
完成此操作后,它将无法编译,我将其留给读者练习其余的解决方案。
回答
我们需要创建一个专门用于保存const Widget&对象的新类型。 IE:
struct ConstWidgetHolder { ConstWidgetHolder(const Widget &a, const Widget &b, const Widget &c, const Widget &d): A(a), B(b), C(c), D(d){} const Widget& A; const Widget& B; const Widget& C; const Widget& D; }; class Test { public: ConstWidgetHolder AccessWidgets() const { return ConstWidgetHolder(A, B, C, D); }
现在,我们会收到以下错误(在gcc 4.3中):
widget.cc: In function 'int main()': widget.cc:51: error: assignment of data-member 'Widget::x' in read-only structure
标准库中使用类似的惯用法与迭代器,即:
class vector { iterator begin(); const_iterator begin() const;
回答
编辑:他删除了他的答案,使我看起来有点愚蠢:)
Flame的答案很危险,是错误的。他的WidgetHolder引用了构造函数中的值对象。构造函数返回后,该按值传递的对象将被销毁,因此我们将保留对销毁对象的引用。
一个使用他的代码的非常简单的示例应用程序清楚地显示了这一点:
#include <iostream> class Widget { int x; public: Widget(int inX) : x(inX){} ~Widget() { std::cout << "widget " << static_cast< void*>(this) << " destroyed" << std::endl; } }; struct WidgetHolder { Widget& A; public: WidgetHolder(Widget a): A(a) {} const Widget& a() const { std::cout << "widget " << static_cast< void*>(&A) << " used" << std::endl; return A; } }; int main(char** argv, int argc) { Widget test(7); WidgetHolder holder(test); Widget const & test2 = holder.a(); return 0; }
输出将是这样的
widget 0xbffff7f8 destroyed widget 0xbffff7f8 used widget 0xbffff7f4 destroyed
为避免这种情况,WidgetHolder构造函数应采用对其要存储为引用的变量的引用。
struct WidgetHolder { Widget& A; public: WidgetHolder(Widget & a): A(a) {} /* ... */ };
回答
最初的查询是如何在包含类为const的情况下如何将WidgetHolder返回为const。 C ++使用const作为函数签名的一部分,因此我们可以拥有const,而同一函数没有const版本。当实例为none const时,将调用none const;当实例为const时,将调用const one。因此,一种解决方案是按功能而不是直接访问微件持有人中的微件。我创建了一个更简单的示例,下面我相信它可以回答原始问题。
#include <stdio.h> class Test { public: Test(int v){m_v = v;} ~Test(){printf("Destruct value = %d\n",m_v);} int& GetV(){printf ("None Const returning %d\n",m_v); return m_v; } const int& GetV() const { printf("Const returning %d\n",m_v); return m_v;} private: int m_v; }; void main() { // A none const object (or reference) calls the none const functions // in preference to the const Test one(10); int& x = one.GetV(); // We can change the member variable via the reference x = 12; const Test two(20); // This will call the const version two.GetV(); // So the below line will not compile // int& xx = two.GetV(); // Where as this will compile const int& xx = two.GetV(); // And then the below line will not compile // xx = 3; }
就原始代码而言,我认为将WidgetHolder作为Test类的成员,然后返回const或者none const对其进行引用,并使Widgets成为拥有者的私有成员,并提供const和每个Widget的none const访问器。
class WidgetHolder { ... Widget& GetA(); const Widget& GetA() const; ... };
然后在主班上
class Test { ... WigetHolder& AccessWidgets() { return m_Widgets;} const WidgetHolder&AcessWidgets() const { return m_Widgets;} private: WidgetHolder m_Widgets; ... };