在结构初始化中保持正确性

我现在在玩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_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 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& 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};
}
链接地址: http://www.djcxy.com/p/41375.html

上一篇: Const correctness in struct initialization

下一篇: Qt Window incorrect size until user event