如何强制实施有关指针数据成员的常量正确性
How to enforce const-correctness regarding pointer data-members
经过工作中的讨论,我们似乎无法对具有指针数据成员的类强制执行"逻辑"常量正确性,如下所示:
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;
}
- 如果类在 C++ 中具有常量或引用类型的非静态数据成员,为什么编译器不提供默认赋值运算符?
- C++中的常量对象或私有/常量数据成员(变量)?
- 如何强制实施有关指针数据成员的常量正确性
- 现在允许重新定义 constexpr 静态数据成员吗?(但不是内联常量)?
- 错误 C2864:'element::next':只能在类 (STRUCT) 中初始化静态常量整数数据成员
- 我应该使用类内发起器初始化静态常量数据成员还是在其类外的定义中初始化静态常量数据成员
- 警告:ISO C++禁止将静态“constexpr char*”数据成员的字符串常量转换为“char*”
- 将非静态数据成员与常量成员进行比较
- 了解常量引用对非成本数据成员的分配
- 移动具有常量数据成员或引用成员的类的ctor
- 多个数据成员的常量初始化
- 将基元类型的数据成员作为常量引用返回
- C++中常量数据成员的用法
- 类中只能初始化静态常量积分数据成员
- 常量成员函数可以修改数据成员
- 静态断言用于检查静态常量类数据成员
- 非静态常量数据成员的意义何在
- 组合常量和非常量引用数据成员的单个类
- 在编码时在类中声明非静态常量数据成员是否合适?
- 将类的静态常量数据成员初始化为类