如何使用 PIMPL 习语实现逻辑常量
How to achieve logical constness with the PIMPL idiom
想象一下 PIMPL 习语的典型实现:
class ObjectImpl;
class Object
{
Object(ObjectImpl* object_impl)
: _impl(object_impl);
private:
ObjectImpl* _impl;
};
我正在寻找的是一种重用相同实现来包装类型 T 的方法,它可以是 ObjectImpl 或 const ObjectImpl,但没有别的:
class ObjectImpl;
class Object
{
Object(T* object_impl)
: _impl(object_impl);
private:
// T being either ObjectImpl or const ObjectImpl
T* _impl;
};
我想要实现的是通过 PIMPL 接口保留逻辑常量,这样编译器就不允许我在包装 const ObjectImpl* 的对象上调用非常量方法。
这基本上只是从 Scott Meyers Effective C++ 书中借用的技巧,但增加了抽象层:
struct SData
{
const Data* data() const { return _data; }
Data* data() { return _data; }
private:
Data* _data:
};
当然我可以将整个 class 复制到一个 class ConstObject 中并让它包装一个 const* Object 而不是 Object* 但我显然是在试图防止代码重复。
我也考虑过模板,但它们对于手头的任务来说似乎有点过分了。首先,我希望 T 只能是 ObjectImpl 或 const ObjectImpl。其次,模板在导出为 DLL 接口时似乎与 PIMPL 的想法背道而驰。有更好的解决方案吗?
我建议采用以下通用设计模式。它浪费了一个额外的指针,但会强制要求 const
对象只能访问私有对象的 const
方法:
class ObjectImpl;
class const_Object {
public:
const_Object(const ObjectImpl* object_impl)
: _impl(object_impl);
// Only const methods
private:
const ObjectImpl* _impl;
};
class Object : public const_Object
{
Object(ObjectImpl* object_impl)
: const_Object(object_impl), _impl(object_impl);
// non-const methods go here.
private:
ObjectImpl* _impl;
};
CRTP.
template<class Storage>
struct const_Object_helper {
Storage* self() { return static_cast<D*>(this); }
Storage const* self() const { return static_cast<D*>(this); }
// const access code goes here, get it via `self()->PImpl()`
};
struct const_Object: const_Object_helper<const_Object> {
const_Object( objectImpl const* impl ):pImpl(impl) {}
private:
objectImpl const* pImpl = nullptr;
objectImpl const* PImpl() const { return pImpl; }
template<class Storage>
friend struct const_Object_helper;
};
struct Object: const_Object_helper<Object> {
// put non-const object code here
Object( objectImpl* impl ):pImpl(impl) {}
operator const_Object() const {
return {PImpl()}; // note, a copy/clone/rc increase may be needed here
}
private:
objectImpl* pImpl = nullptr;
objectImpl const* PImpl() const { return pImpl; }
objectImpl* PImpl() { return pImpl; }
template<class Storage>
friend struct const_Object_helper;
};
这是零运行时开销版本,但需要公开 const_Object_helper
和 Object_helper
的实现。因为它只涉及将东西转发给实际的实现,所以这似乎相对无害。
您可以通过用纯虚拟 objectImpl const* get_pimpl() const = 0
和 objectImpl* get_pimpl() = 0
替换助手的 CRTP 部分来消除这种需要,然后在派生类型中实现它们。
另一种有点疯狂的方法是使用 any
增强类型擦除操作(您还想教授有关 const
的类型擦除机制,并且 super_any
更少的接口可以隐式转换而不做另一层包装)。
这里我们定义了某些操作,比如打印、跳舞和布吉:
auto const print = make_any_method<void(std::ostream&), true>(
[](auto&&self, std::ostream& s) {
s << decltype(self)(self);
}
);
auto const dance = make_any_method<void()>(
[](auto&&self) {
decltype(self)(self).dance();
}
);
auto const dance = make_any_method<double(), true>(
[](auto&&self) {
return decltype(self)(self).boogie();
}
);
现在我们创建两种类型:
using object = super_any< decltype(print), decltype(dance), decltype(boogie) > object;
using const_object = super_any< decltype(print), decltype(boogie) >;
接下来,增加 super_any
从具有严格较弱要求的来源分配的能力。
我们的object o;
可以(o->*dance)()
。我们的const_object co;
可以double d = (co->*boogie)();
.
支持 print
、boogie
和 dance
描述的操作的任何内容都可以存储在 object
中,加上 any
的要求(复制、销毁、分配)。什么都行。
同样,const_object
支持任何可以用 print
和 boogie
和 copy/destroy/assign 描述的内容。
来自 object
或 const_object
的派生类型可以轻松添加运算符重载功能。
这项技术很先进。您可以使用 boost::type_erasure
来完成,可能比这个草图更圆滑。
想象一下 PIMPL 习语的典型实现:
class ObjectImpl;
class Object
{
Object(ObjectImpl* object_impl)
: _impl(object_impl);
private:
ObjectImpl* _impl;
};
我正在寻找的是一种重用相同实现来包装类型 T 的方法,它可以是 ObjectImpl 或 const ObjectImpl,但没有别的:
class ObjectImpl;
class Object
{
Object(T* object_impl)
: _impl(object_impl);
private:
// T being either ObjectImpl or const ObjectImpl
T* _impl;
};
我想要实现的是通过 PIMPL 接口保留逻辑常量,这样编译器就不允许我在包装 const ObjectImpl* 的对象上调用非常量方法。
这基本上只是从 Scott Meyers Effective C++ 书中借用的技巧,但增加了抽象层:
struct SData
{
const Data* data() const { return _data; }
Data* data() { return _data; }
private:
Data* _data:
};
当然我可以将整个 class 复制到一个 class ConstObject 中并让它包装一个 const* Object 而不是 Object* 但我显然是在试图防止代码重复。
我也考虑过模板,但它们对于手头的任务来说似乎有点过分了。首先,我希望 T 只能是 ObjectImpl 或 const ObjectImpl。其次,模板在导出为 DLL 接口时似乎与 PIMPL 的想法背道而驰。有更好的解决方案吗?
我建议采用以下通用设计模式。它浪费了一个额外的指针,但会强制要求 const
对象只能访问私有对象的 const
方法:
class ObjectImpl;
class const_Object {
public:
const_Object(const ObjectImpl* object_impl)
: _impl(object_impl);
// Only const methods
private:
const ObjectImpl* _impl;
};
class Object : public const_Object
{
Object(ObjectImpl* object_impl)
: const_Object(object_impl), _impl(object_impl);
// non-const methods go here.
private:
ObjectImpl* _impl;
};
CRTP.
template<class Storage>
struct const_Object_helper {
Storage* self() { return static_cast<D*>(this); }
Storage const* self() const { return static_cast<D*>(this); }
// const access code goes here, get it via `self()->PImpl()`
};
struct const_Object: const_Object_helper<const_Object> {
const_Object( objectImpl const* impl ):pImpl(impl) {}
private:
objectImpl const* pImpl = nullptr;
objectImpl const* PImpl() const { return pImpl; }
template<class Storage>
friend struct const_Object_helper;
};
struct Object: const_Object_helper<Object> {
// put non-const object code here
Object( objectImpl* impl ):pImpl(impl) {}
operator const_Object() const {
return {PImpl()}; // note, a copy/clone/rc increase may be needed here
}
private:
objectImpl* pImpl = nullptr;
objectImpl const* PImpl() const { return pImpl; }
objectImpl* PImpl() { return pImpl; }
template<class Storage>
friend struct const_Object_helper;
};
这是零运行时开销版本,但需要公开 const_Object_helper
和 Object_helper
的实现。因为它只涉及将东西转发给实际的实现,所以这似乎相对无害。
您可以通过用纯虚拟 objectImpl const* get_pimpl() const = 0
和 objectImpl* get_pimpl() = 0
替换助手的 CRTP 部分来消除这种需要,然后在派生类型中实现它们。
另一种有点疯狂的方法是使用 any
增强类型擦除操作(您还想教授有关 const
的类型擦除机制,并且 super_any
更少的接口可以隐式转换而不做另一层包装)。
这里我们定义了某些操作,比如打印、跳舞和布吉:
auto const print = make_any_method<void(std::ostream&), true>(
[](auto&&self, std::ostream& s) {
s << decltype(self)(self);
}
);
auto const dance = make_any_method<void()>(
[](auto&&self) {
decltype(self)(self).dance();
}
);
auto const dance = make_any_method<double(), true>(
[](auto&&self) {
return decltype(self)(self).boogie();
}
);
现在我们创建两种类型:
using object = super_any< decltype(print), decltype(dance), decltype(boogie) > object;
using const_object = super_any< decltype(print), decltype(boogie) >;
接下来,增加 super_any
从具有严格较弱要求的来源分配的能力。
我们的object o;
可以(o->*dance)()
。我们的const_object co;
可以double d = (co->*boogie)();
.
支持 print
、boogie
和 dance
描述的操作的任何内容都可以存储在 object
中,加上 any
的要求(复制、销毁、分配)。什么都行。
同样,const_object
支持任何可以用 print
和 boogie
和 copy/destroy/assign 描述的内容。
来自 object
或 const_object
的派生类型可以轻松添加运算符重载功能。
这项技术很先进。您可以使用 boost::type_erasure
来完成,可能比这个草图更圆滑。