结构初始化中的const正确性

时间:2018-02-14 13:33:18

标签: c++ const-correctness

我正在玩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限定符,因为我认为它为未来的开发人员提供了信息:它清楚地表明函数不会修改el 。仍然可以通过data中的其他函数修改字段important_structure

我的问题是:如何处理在costructor中初始化并在其他函数中更改的字段? 大多数const正确性处理简单的答案,但毫无疑问(我认为)处理将const参数传递给数据结构然后由其他人更改此类数据结构的情况。

感谢您的回复

3 个答案:

答案 0 :(得分:2)

el作为const引用传递并不意味着该函数在函数运行期间不会更改el,这意味着由于此函数调用,{ {1}}根本没有改变。通过将el的地址放入非常数el,您违反了该承诺。

因此,如果您确实想要更改数据,那么干净的解决方案就是删除data。因为它不为未来的开发人员提供信息,但具有误导性。抛弃const会非常糟糕。

答案 1 :(得分:1)

让我们使用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字段,可以通过非常受控制的方式访问。

让({1}}的简化版本将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; } 个对象。同样,结果可能不是我们首先需要的。

中间有第三种方式:对象在所有者外部实例化,移动,使用move semantics

举个例子,让我们给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

或现有的,提供左值所需的参考,感谢std::move

important_structure<Data> s(Data(42));

在第二个示例中,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类成为容器,正如您在评论中提到的那样,以便{GH}可以从外部访问important_structure。让我们为data类提供这样的方法:

important_structure

现在,我们可以使用const T & owneddata() { return data; } 这样的const方法:

data

但是要求`数据&#39;非const方法将无法编译:

important_structure<Data> s(Data(42));

std::cout << s.owneddata().readSomething() << std::endl;

如果需要它(希望不是),请公开一个非const引用:

s.owneddata().changeSomething(1000); //not compiling ...

现在T & writablereference() { return data; } 字段已完全处理:

data

答案 2 :(得分:0)

使用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}; } 无法更改,那么您希望以这样的方式编写它:

SELECT
 COLUMN_NAME
FROM
 information_schema.COLUMNS
WHERE
 TABLE_NAME = 'city';