在结构初始化中保持正确性
我现在在玩C ++和const-correctness。 假设你有以下结构
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 *'
现在,我知道将const int*
为int*
是不安全的。 问题是我有一个数据结构,我不想把字段data
作为常量。
但是,我不想在构造const
中删除const
限定符,因为我认为它对未来的开发人员是el
:它清楚地表明el
不会被函数修改。 仍然可以通过important_structure
一些其他函数来修改现场data
。
我的问题是:如何处理在构造器中初始化并在其他函数中进行了更改的字段? 大多数常量正确性处理简单的答案,但没有问题(我认为)处理的情况是const参数传递给数据结构,然后这样的数据结构被其他人改变。
谢谢你的回复
将el
作为const
引用传递并不仅仅意味着函数在函数运行期间不会改变el
,这意味着由于此函数调用, el
根本不会被改变。 通过将el
的地址放入非常量data
,您违反了该承诺。
所以,干净的解决方案,如果你确实想改变数据,就是去掉const
。 因为它对于未来的开发者来说没有提供信息,但是会让人误解。 在这里抛弃const
会非常糟糕。
让我们使用一个简单的类作为T
type的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
istance的指针被留下。 如果有人错误地称它为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);
}
仍然可以使用临时值作为所需的右值传递一个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
常量方法:
important_structure<Data> s(Data(42));
std::cout << s.owneddata().readSomething() << std::endl;
但是调用'Data'非const方法将不会编译:
s.owneddata().changeSomething(1000); //not compiling ...
如果需要它(希望不是),请暴露一个非const引用:
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};
}
链接地址: http://www.djcxy.com/p/41375.html