如何在指针数据成员方面实施const正确性

3
在工作中的讨论中,我们似乎无法将“逻辑”const正确性强制应用于具有指针数据成员的类,例如:
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指针成员的非const函数,同时仍然允许从其他非const函数中调用。这意味着将指针声明为std :: shared_ptr<const Widget>(即const Widget*)是不行的。
此外,我们希望在管理器的生命周期内使指针引用另一个Widget,因此我们不想将其保留为数据成员(也无法通过引用保存)。
当然,在这里强制执行所有“按位”const正确性,因为WidgetManager的任何数据成员都不能在const方法内修改(包括由_pW指向的特定Widget),但我们希望实现“逻辑”const正确性,即连带指向的成员也不能被修改。
我们唯一想到的是向Widget添加const和non-const“获取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()
有没有一种方法可以实现这个目的?

1
http://en.cppreference.com/w/cpp/experimental/propagate_const - Jarod42
4个回答

2

考虑通过反映this的const属性到指向对象上的成员函数来访问您的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
}

我确实后悔const访问器返回副本...但这里有可靠的确认 - Drew Dormann

2
您可以使用(或重新实现)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;
};

Demo


0
一个简单的解决方案是将const和non-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.

0
除了这里提出的内容,您还可以基于共享指针引入“拥有指针”的概念:
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;
}

owning_ptr(T* data): 这是不要使用的构造函数。只需使用 shared_ptr 即可。 - Jarod42
@Jarod42 这里无关紧要。 - Teivaz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接