如何强制实施有关指针数据成员的常量正确性

How to enforce const-correctness regarding pointer data-members

本文关键字:数据成员 常量 正确性 指针 何强制      更新时间:2023-10-16

经过工作中的讨论,我们似乎无法对具有指针数据成员的类强制执行"逻辑"常量正确性,如下所示:

class Widget {
public:
void Foo();
void FooConst() const;
};
class WidgetManager {
public:
WidgetManager() : _pW(std::shared_ptr<Widget>(new Widget())) { }
void ManagerFoo()
{
_pW->Foo();         // should be OK, will not compile if declared as "const Widget*"
_pW->FooConst();    // should be OK
}
void ManagerFooConst() const
{
_pW->Foo();         // should NOT be OK, will not compile if declared as "const Widget*"
_pW->FooConst();    // should be OK
}
void RegenerateWidget()
{
_pW = std::shared_ptr<Widget>(new Widget());
}
private:
std::shared_ptr<Widget> _pW;
};

可以看出,我们希望WidgetManager::ManagerFooConst()不能调用WidgetManager指针成员的非常量函数,同时仍然允许从WidgetManager的其他非常量函数调用它们。这意味着,将指针声明为std::shared_ptr<const Widget>(即const Widget*(已出局。

此外,我们希望可以选择在管理器的生命周期内使指针引用另一个Widget,因此我们真的不想将其保存为数据成员(并且不能通过引用来保存它(。

当然,这里强制执行所有"按位"的常量正确性,因为不能从 const 方法中修改WidgetManager的数据成员(包括_pW指向的特定Widget(,但我们希望实现"逻辑"常量正确性,即使是指向的成员也不能修改。

我们唯一想到的是添加一个常量和非常量"this的getters "到Widget

class Widget {
public:
void Foo();
void FooConst() const;
Widget* GetPtr()    { return this; }
const Widget* GetConstPtr() const   { return this; }
};

并恢复为直接使用这些而不是箭头运算符:

void WidgetManager::ManagerFoo()
{
// shouldn't use "->" directly (lint?)
_pW->GetPtr()->Foo();
_pW->GetPtr()->FooConst();
//_pW->GetConstPtr()->Foo();        // this won't compile (good)
_pW->GetConstPtr()->FooConst();
}
void WidgetManager::ManagerFooConst() const
{
// shouldn't use "->" directly (lint?)
_pW->GetPtr()->Foo();               // shouldn't be used (lint?)
_pW->GetPtr()->FooConst();          // shouldn't be used (lint?)
//_pW->GetConstPtr()->Foo();        // this won't compile (good)
_pW->GetConstPtr()->FooConst();
}

但这太丑陋了,而且编译器绝对无法强制执行。

具体来说,试图为Widget*const Widget*超载operator->似乎并没有改变任何事情:ManagerFooConst()仍然能够打电话给_pW->Foo()

有没有办法实现这一目标?

您可以使用(或重新实现(std::experimental::propagate_const

然后你的代码将是:

class Widget {
public:
void Foo();
void FooConst() const;
};
class WidgetManager {
public:
WidgetManager() : _pW(std::make_shared<Widget>()) {}
void ManagerFoo()
{
_pW->Foo();      // OK
_pW->FooConst(); // OK
}
void ManagerFooConst() const
{
_pW->Foo();         // not compile
_pW->FooConst();    // OK
}
private:
std::experimental::propagate_const<std::shared_ptr<Widget>> _pW;
};

演示

考虑通过反映指向对象上this恒定性的成员函数访问shared_ptr

class WidgetManager {
...
private:
std::shared_ptr<Widget> _pW;
std::shared_ptr<Widget>& get_widget()
{
return _pW;
}
const std::shared_ptr<const Widget> get_widget() const
{
return _pW;
}
}

您将在此处收到您期望的一个错误。

void ManagerFoo()
{
get_widget()->Foo();         // will be OK, will not compile if declared as "const Widget*"
get_widget()->FooConst();    // will be OK
}
void ManagerFooConst() const
{
get_widget()->Foo();         // will NOT be OK
get_widget()->FooConst();    // will be OK
}

一个简单的解决方案是使常量和非常量管理器成为两种不同的类型:

template<class TWidget>
class WidgetManager {
// ...
private:
std::shared_ptr<TWidget> _pW;
};
WidgetManager<const Widget> const_wm;
const_wm.ManagerFoo(); // fail: _pW->Foo(): call non-const function of *_pw: a const Widget.

除了这里提出的之外,您还可以引入基于共享指针的"拥有指针"的概念:

template<class T>
class owning_ptr {
public:
owning_ptr(T* data) : mData{data} {}
// Put as many constructors as you need
T* operator->() { return mData.get(); }
const T* operator->() const { return mData.get(); }
private:
std::shared_ptr<T> mData;    
};

此类具有与共享指针相同的功能,但拥有数据的概念不同。

您将像使用共享指针一样使用它:

class WidgetManager {
...
private:
owning_ptr<Widget> _pW;
}