结构初始化中的常量正确性



我现在正在玩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在这里会非常糟糕。

让我们使用一个简单的类作为Timportant_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_structureData的实例保存为私有字段:

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; }

现在,我们可以使用dataconst 方法,如下所示:

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& eldata(&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};
}

最新更新