我现在正在玩C++和恒常正确性。 假设您具有以下结构
template <typename T>
struct important_structure {
public:
T* data;
int a;
important_structure(const T& el, int a);
void change();
};
template <typename T>
void important_structure<T>::change() {
//alter data field in some way
}
template <typename T>
important_structure <T>::important_structure(const T& el, int a) : data(&el), a(a) //error line {
};
int main() {
important_structure<int>* s = new important_structure<int>{5, 3};
}
使用std=c++11
进行编译时,编译器返回以下错误:
从"const int*"到"int*"的转换无效
现在,我知道向int*
投const int*
是不安全的。问题是我有一个数据结构,我不想将字段data
作为常量。
但是,我不想删除构造函数中的const
限定符,因为我认为它对未来的开发人员来说是有益的:它清楚地表明el
不会被函数修改。仍然data
字段可能会被important_structure
中的其他函数修改。
我的问题是:如何处理在协构函数中初始化并在其他函数中更改的字段? 大多数 const 正确性处理简单的答案,但没有问题(我认为)处理将 const 参数传递给数据结构然后该数据结构被其他人更改的情况。
感谢您的任何友好回复
将el
作为const
引用传递不仅意味着函数在函数运行期间不会el
更改,还意味着由于此函数调用,el
根本不会更改。通过将el
的地址放入非常量data
,您违反了这一承诺。
因此,如果您确实想更改数据,干净的解决方案是删除const
。 因为它对未来的开发人员没有信息,而是误导。抛弃const
在这里会非常糟糕。
让我们使用一个简单的类作为T
important_struct
类型:
class Data
{
public:
Data() : something(0){}
Data(int i) : something(i){}
Data(const Data & d) : something(d.something){}
//non-const method: something can be modified
void changeSomething(int s){ something += s; }
//const method: something is read-only
int readSomething() const { return something; }
private:
int something;
};
这个类有一个非常简单但封装良好的状态,即int something
字段,可以通过方法以非常可控的方式访问它。
让(简化版本)important_structure
将Data
的实例保存为私有字段:
template <typename T>
struct important_structure
{
public:
important_structure(T * el);
void change();
int read() const;
private:
T* data;
};
我们可以通过以下方式将Data
实例分配给important_structure
实例:
important_structure<Data> s(new Data());
实例在构造中分配:
template <typename T>
important_structure <T>::important_structure(T * el) : data(el) {}
现在最大的问题是:important_structure
拥有它所持有的Data
实例的所有权吗?答案必须在文档中明确说明。
如果是,important_structure
必须负责内存清理,例如 需要像这样的析构函数:
template<typename T>
important_structure<T>::~important_structure()
{
delete data;
}
请注意,在本例中:
Data * p = new Data()
// ...
important_structure<Data> s(p);
//p is left around ...
另一个指向Data
的指针被保留在周围。如果有人错误地打电话给delete
怎么办?或者,更糟糕的是:
Data d;
// ...
important_structure<Data> s(&p); //ouch
更好的设计将允许important_structure
拥有自己的Data
实例:
template <typename T>
struct important_structure
{
public:
important_structure();
void change();
// etc ...
private:
T data; //the instance
};
但这可能过于简单化,或者只是不需要。
可以important_structure
复制它将拥有的实例:
template<typename T>
important_structure<T>::important_structure(const T &el)
{
data = el;
}
后者是问题中提供的构造函数:传递的对象不会被触摸,而是被复制。显然,现在周围有两个相同的Data
物体。同样,结果不可能是我们首先需要的。
还有第三种方式,在中间:对象在所有者外部实例化,并使用移动语义移动到它。
例如,让我们给Data
一个移动分配运算符:
Data & operator=(Data && d)
{
this->something = d.something;
d.something = 0;
return *this;
}
让我们important_structure
提供一个接受T
的右值引用的构造函数:
important_structure(T && el)
{
data = std::move(el);
}
仍然可以使用临时作为所需的 rvalue 传递Data
实例:
important_structure<Data> s(Data(42));
或现有的,从左值提供所需的引用,感谢 std::move:
Data d(42);
// ...
important_structure<Data> x(std::move(d));
std::cout << "X: " << x.read() << std::endl;
std::cout << "D: " << d.readSomething() << std::endl;
在第二个示例中,important_structure
持有的副本被认为是好的,而另一个副本则处于有效但未指定的状态,只是为了遵循标准的库习惯。
恕我直言,此模式在代码中更清楚地说明,特别是如果认为此代码无法编译:
Data d(42);
important_structure<Data> x (d);
任何想要important_structure
实例的人都必须提供一个临时Data
实例或使用std::move
显式移动现有实例。
现在,正如您在评论中要求的那样,让important_structure
类成为一个容器,以便以某种方式从外部访问data
。让我们给important_structure
类一个这样的方法:
const T & owneddata() { return data; }
现在,我们可以使用data
const 方法,如下所示:
important_structure<Data> s(Data(42));
std::cout << s.owneddata().readSomething() << std::endl;
但是对"数据"非常量方法的调用不会编译:
s.owneddata().changeSomething(1000); //not compiling ...
如果需要它(希望不需要),请公开一个非常量引用:
T & writablereference() { return data; }
现在,data
字段可供完全使用:
s.writablereference().changeSomething(1000); //non-const method called
std::cout << s.owneddata().readSomething() << std::endl;
使用const T& el
和data(&el)
是一个非常糟糕的主意,因为它意味着你可以写:
new important_structure<int>{5, 3};
但是写入new important_structure<int>{5, 3};
将导致数据保存一个地址,该地址在调用构造函数后立即不再有效。
如果您希望可以更改点data
,但不能更改指针指向的value
,则需要这样写:
template <typename T>
struct important_structure {
public:
T const * data;
int a;
important_structure(T const * el, int a);
void change();
};
template <typename T>
void important_structure<T>::change() {
//alter data field in some way
}
template <typename T>
important_structure <T>::important_structure( T const * el, int a) : data(el), a(a) { //error line
};
int main() {
int i = 5;
important_structure<int>* s = new important_structure<int>{&i, 3};
}