如何强制执行有关指针数据成员的常量正确性
How to enforce const-correctness regarding pointer data-members
经过工作讨论,我们似乎无法对具有指针数据成员的 class 执行 "logical" 常量正确性,因此:
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
,因此我们真的不想将其作为数据成员保存(并且可以'不作为参考)。
当然,这里强制执行所有 "bitwise" const 正确性,因为不能从 const 方法中修改 WidgetManager
的数据成员(包括由_pW
),但我们希望实现 "logical" 常量正确性,即使指向的成员也无法修改。
我们唯一想到的就是在 Widget
中添加一个 const 和非常量 "getters of this
":
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()
.
有办法实现吗?
一个简单的解决方案是将 const 和非常量管理器分为两种不同的类型:
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.
考虑通过成员函数访问您的 shared_ptr
,该成员函数将 this
的常量性反映到指向的对象上。
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
}
除了这里提出的,你还可以引入一个基于共享指针的"owning pointer"概念:
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 与共享指针具有相同的功能,但拥有数据的概念不同。
您可以像使用共享指针一样使用它:
class WidgetManager {
...
private:
owning_ptr<Widget> _pW;
}
您可能会使用(或重新实现)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;
};
经过工作讨论,我们似乎无法对具有指针数据成员的 class 执行 "logical" 常量正确性,因此:
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
,因此我们真的不想将其作为数据成员保存(并且可以'不作为参考)。
当然,这里强制执行所有 "bitwise" const 正确性,因为不能从 const 方法中修改 WidgetManager
的数据成员(包括由_pW
),但我们希望实现 "logical" 常量正确性,即使指向的成员也无法修改。
我们唯一想到的就是在 Widget
中添加一个 const 和非常量 "getters of this
":
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()
.
有办法实现吗?
一个简单的解决方案是将 const 和非常量管理器分为两种不同的类型:
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.
考虑通过成员函数访问您的 shared_ptr
,该成员函数将 this
的常量性反映到指向的对象上。
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
}
除了这里提出的,你还可以引入一个基于共享指针的"owning pointer"概念:
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 与共享指针具有相同的功能,但拥有数据的概念不同。
您可以像使用共享指针一样使用它:
class WidgetManager {
...
private:
owning_ptr<Widget> _pW;
}
您可能会使用(或重新实现)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;
};