通过扩展添加可变性
Adding mutability through extension
我有一个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>'
}
- 将成员变量添加到共享库中的类中,不会破坏二进制兼容性吗
- 如何在C++中从两个单独的for循环中添加两个数组
- POCO::PostgreSQL:如何将std::vector支持添加到`Binder::bind`
- 如何仅为一个函数添加延迟
- 如何防止 c++ 在从浮点型转换为双精度型(不适用于 IO)时添加额外的小数?
- 使用std::transform将一个范围的元素添加到另一个范围中
- 如何将更多文件夹添加到c++include路径
- 如何将元素添加到数组的线程安全函数?
- QT通过C++添加映射QML项目
- 如何将点击的信号和插槽添加到qt中的自定义按钮中
- 如何使用重载的相等(==)运算符向测试用例添加描述
- 为什么Mat类的两个对象可以在不重载运算符+的情况下添加
- 如何防止clang格式在流运算符调用之间添加换行符<<
- 只能向C++添加一定数量的字符
- Qt和C++:将QLineEdit添加到QTabWidget中
- 将QIcon添加到QTableView单元格
- 为什么我必须在C++中添加一个赋值符号来声明一个数组
- 为什么除非添加括号,否则构造函数上的模板替换会失败?
- 将图像添加到资源文件夹UWP C++
- 通过扩展添加可变性