通过扩展添加可变性

Adding mutability through extension

本文关键字:可变性 添加 扩展      更新时间:2023-10-16

我有一个Data类和一个提供访问Data的方法的Wrapper类。WrapperMutable类扩展Wrapper以添加修改Data的方法。

#include <memory>
using namespace std;
class Data {
public:
    void update();  // non-const method which modifies internal state of Data
};
class Wrapper
{
public:
  Wrapper(shared_ptr<Data const> data) : myData(data) {}  // accepts const pointer
  // ...bunch of functions providing read-only access to aspects of the Data...
protected:
  shared_ptr<Data const> myData;  // stores const pointer
};
// Extend Wrapper with methods to modify the wrapped Data.
class WrapperMutable : public Wrapper
{
public:
  WrapperMutable(shared_ptr<Data> data) : Wrapper(data) {}
  // ERROR: invoking non-const method on const object:
  void updateData() { myData->update(); } 
};

问题当然是被包装的Data对象的const性,这意味着WrapperMutable不能修改它

我曾考虑将Wrapper更改为接受并存储非const Data,但通常客户端本身只能访问const Data,因此它们将被迫使用const_cast或复制来创建Wrapper

因此,我能够实现这一点的唯一方法是在WrapperMutable类中保留一个额外的非const指针,并在可变上下文中使用它:

class WrapperMutable : public Wrapper
{
public:
  WrapperMutable(shared_ptr<Data> data) : Wrapper(data), myMutableData(data) {}
  // Use myMutableData instead of the const myData
  void updateData() { myMutableData->update(); }
private:
  shared_ptr<Data> myMutableData;  // non-const pointer to the same Data as in Wrapper
};

有更好的方法吗?很明显,从Wrapper派生WrapperMutable是我问题的根源,但我也不想在WrapperMutable中重新实现Wrapper的所有方法。

继承表达了一种"Kind of"关系。

惊愕不是一种关系。

常量与可变事物是一种截然不同的事物。

shared_ptr的模型本身展示了如何表达这种关系。可变数据的shared_ptr可以转换为常量数据的shared_ptr,但不能反过来。

你可以这样表达这种关系:

#include <iostream>
#include <memory>
struct Data
{
};
struct const_data_wrapper
{
    const_data_wrapper(std::shared_ptr<const Data> p) : _impl(std::move(p)) {}
    void which() const {
        std::cout << "const" << std::endl;
    }
private:
    std::shared_ptr<const Data> _impl;
};
struct data_wrapper
{
    data_wrapper(std::shared_ptr<Data> p) : _impl(std::move(p)) {}
    const_data_wrapper as_const() const {
        return const_data_wrapper(_impl);
    }
    void which() const {
        std::cout << "not const" << std::endl;
    }
private:
    std::shared_ptr<Data> _impl;
};
using namespace std;
auto main() -> int
{
    auto w1 = data_wrapper(make_shared<Data>());
    auto w2 = w1.as_const();
    w1.which();
    w2.which();
    return 0;
}

输出:

not const
const

我认为,WrapperMutable中不需要shared_ptr<>,一个原始指针就可以了:

class WrapperMutable : public Wrapper
{
public:
  WrapperMutable(Data* data):
    Wrapper{shared_ptr<Data const>{data}},
    myMutableData{data} {}
  // Use myMutableData instead of the const myData
  void updateData() { myMutableData->update(); }
private:
  Data* myMutableData;  // non-const pointer to the same Data as in Wrapper
};

至少这样可以避免增加和减少引用计数器。

从软件设计的角度来看,你确定WrapperMutable"是"Wrapper吗?我的直觉是,你在某个地方违反了单一责任原则。这是我所见过的设计问题的主要原因之一。

顺便说一下:如果你真的需要shared_ptr<>,请重新考虑。它经常被过度用作垃圾收集的替代品。当你真的想声明共享所有权时,请使用它。无所有权时首选原始指针,唯一所有权时首选unique_ptr<>。原因是,与原始指针和unique_ptr<>相比,shared_ptr<>不是免费的。它需要引用计数器的额外增量和减量,当你取消引用它时,通常需要额外的间接级别。另一方面,unique_ptr<>将创建与原始指针相同的代码,并在所有位置正确地删除。

如果提供对数据各方面的只读访问的函数束不需要增加shared_ptr,而只需要(const)对指向数据的访问,那么以下oop方法将起作用:

class BaseWrapper {
public:
    // ...bunch of functions providing read-only access to aspects of the Data...
protected:
    virtual const Data* getData() = 0;
    virtual ~BaseWrapper(){}
};
class WrapperConst: public BaseWrapper {
public:
   WrapperConst(shared_ptr<Data const> data) : myData(data) {}
protected:
    const Data* getData() {
        return myData.get();
    };
private:
    shared_ptr<Data const> myData;
};
class WrapperMutable : public BaseWrapper {
public:
    WrapperMutable(shared_ptr<Data> data) : myData(data) {}
    void updateData() { myData->update(); }
protected:
   const Data* getData() {
        return myData.get();
    };
private:
    shared_ptr<Data> myData;
};

优点:您不需要另一份shared_ptr

缺点:您现在使用了原本不需要的虚拟函数。只读函数无法访问shared_ptr,因此可能无法复制它。更多样板。

#include <memory>
using namespace std;
class Data {
public:
    void update() {}  // non-const method which modifies internal state of Data
};

//Basic wrapper
template <typename D>
class BaseWrapper {
    public:     
        BaseWrapper(shared_ptr<D> data) : myData(data) {}
    protected:
        shared_ptr<D> myData;
};
template <typename D, bool const = std::is_const<D>::value>
class Wrapper : public BaseWrapper<D>
{
};
//Const-Version
template <typename D>
class Wrapper<D, true> : public BaseWrapper<D>
{
public:
    Wrapper(shared_ptr<D> data) : BaseWrapper(data) {}
};
//Non-Const-Version
template <typename D>
class Wrapper<D, false> : public BaseWrapper<D>
{
public:
    Wrapper(shared_ptr<D> data) : BaseWrapper(data) {}
    void updateData() { myData->update(); }
};

int main()
{
    Wrapper<Data> a(nullptr);
    Wrapper<const Data> b(nullptr);
    a.updateData();
    b.updateData();//error C2039: 'updateData': is not a member of 'Wrapper<const Data,true>'
}