题
我在弄清楚 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() 中的代码可以编译吗?
非常感谢。
解决方案
之所以能够编译,是因为虽然 WidgetHolder 是一个 const 对象,但这种 const 性不会自动应用于指向 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;
不可更改的.AccessWidgets():
此时,您正在创建一个 WidgetHolder 类型的新对象。该对象不受 const 保护。
您还在 WidgetHolder 中创建新的小部件,而不是对 Wdiget 的引用。
你的 WidgetHolder
将持有无效的引用(指针)。您将堆栈上的对象传递给构造函数,然后保存对其(临时)地址的引用。这肯定会打破。
您应该只将引用分配给与引用本身具有相同(或更长)生命周期的对象。
如果必须保留引用,请将引用传递给构造函数。更好的是,根本不要保留参考资料,只需制作副本即可。
编辑:他删除了他的答案,让我看起来有点愚蠢:)
弗莱姆的回答是危险的错误。他的 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 版本。当实例不是const时,调用none const;当实例是const时,调用const。因此,解决方案是通过函数而不是直接访问小部件持有者中的小部件。我在下面创建了一个更简单的示例,我相信它回答了原来的问题。
#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 或非 const 引用,并使 Widgets 成为 Holder 的私有成员,并提供一个每个 Widget 的 const 和 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;
...
};