2015-11-10 77 views
3

我有一个Data类和一个Wrapper类,它提供访问Data的方法。 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(); } 
}; 

当然,问题是与const -ness包裹Data对象的,这意味着WrapperMutable不能修改它。

我认为改变Wrapper接受和储存非constData,但通常是客户本身将只需要const Data访问,所以那么他们将被迫const_cast或以创造一个Wrapper复制。

所以我已经能够实现这一目标的唯一途径是通过保持一个附加的非const指针在WrapperMutable类,并使用该在可变背景:

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的方法。

+0

你就不能更改'shared_ptr的 myData'到'常量的shared_ptr myData'?这也允许定义'Wrapper(Data * const data):myData(data){}' –

+0

“,但客户端本身通常只能访问'const Data'” - 我认为这是导致问题。如果他们不能修改数据,那么也不应该将该数据传递给任何可以修改它的人。 – Petr

+0

@SimonKraemer我认为这仍然会强制客户端持有指向'const Data'的指针来执行'const_cast',不是吗? – atkins

回答

1

继承表示“种类”关系。

恒常不是“种类”关系。

一个常量事物是一个非常不同的事物,一个可变的事物。

shared_ptr本身的模型显示了如何表达这种关系。可变数据的shared_ptr可转换为const Data的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 
1

如果一堆提供只读访问数据方面的功能不需要递增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,因此它们可能不会复制它。更多的样板。

1

我想,你不需要在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<>确实是你在这里所需要的。它经常被滥用作为垃圾收集的替代品。使用它,当你真的想表明共享所有权。首选原始指针为无所有权,​​为唯一所有权。原因是,与原始指针和​​相比,shared_ptr<>不是免费的。它需要引用计数器的额外增量和减量,并且通常在您取消引用时增加额外的间接级别。另一方面​​将创建与原始指针相同的代码,并将正确的位置删除遍布整个地方。

1
#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>' 
}