“Forward-unbreakable”访问器类模板 [C++]
-
19-09-2019 - |
题
除非我完全弄错了,否则 getter/setter 模式是用于两件事的常见模式:
- 创建一个私有变量,以便可以使用它,但永远不会修改它,只需提供一个
getVariable
方法(或者,更罕见的是,只能通过仅提供一个setVariable
方法)。 - 为了确保将来,如果您碰巧遇到一个问题,一个好的解决方案就是在变量进入和/或离开类之前简单地对其进行处理,那么您可以使用实际的实现来处理该变量在 getter 和 setter 方法上,而不是简单地返回或设置值。这样,更改就不会传播到代码的其余部分。
问题#1:我是否错过了访问器的任何使用,或者我的任何假设是否不正确?我不确定我的观点是否正确。
问题2:是否有任何类型的模板优点可以让我不必为我的成员变量编写访问器?我没有找到。
问题#3:下面的类模板是否是实现 getter 而无需实际编写访问器的好方法?
template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
typedef T Type;
};
template <typename T,class Owner>
class Getter
{
public:
friend class TemplateParameterIndirection<Owner>::Type; // Befriends template parameter
template <typename ... Args>
Getter(Args args) : value(args ...) {} // Uses C++0x
T get() { return value; }
protected:
T value;
};
class Window
{
public:
Getter<uint32_t,Window> width;
Getter<uint32_t,Window> height;
void resize(uint32_t width,uint32_t height)
{
// do actual window resizing logic
width.value = width; // access permitted: Getter befriends Window
height.value = height; // same here
}
};
void someExternalFunction()
{
Window win;
win.resize(640,480); // Ok: public method
// This works: Getter::get() is public
std::cout << "Current window size: " << win.width.get() << 'x' << win.height.get() << ".\n";
// This doesn't work: Getter::value is private
win.width.value = 640;
win.height.value = 480;
}
这对我来说看起来很公平,我什至可以重新实现 get
通过使用其他一些部分模板专业化技巧来实现逻辑。同样的情况也适用于某种 Setter 甚至 GetterSetter 类模板。
你怎么看?
解决方案
虽然从实施的角度来看,该解决方案很简洁,但从架构上来说,它只完成了一半。Getter/Setter 模式的要点是让类能够控制其数据并减少耦合(即其他班级知道 如何 数据被存储)。该解决方案实现了前者,但不能完全实现后者。
事实上,另一个类现在必须知道两件事 - 变量的名称和 getter 上的方法(即 .get()
) 而不是一个 - 例如 getWidth()
. 。这会导致耦合增加。
话虽如此,这却是众所周知的建筑问题。归根结底,这并不重要。
编辑 好吧,现在废话了,这里是使用运算符的 getter 版本,所以你不必这样做 .value
或者 .get()
template <class T>
struct TemplateParameterIndirection // This hack works for MinGW's GCC 4.4.1, dunno others
{
typedef T Type;
};
template <typename T,class Owner>
class Getter
{
public:
friend TemplateParameterIndirection<Owner>::Type; // Befriends template parameter
operator T()
{
return value;
}
protected:
T value;
T& operator=( T other )
{
value = other;
return value;
}
};
class Window
{
public:
Getter<int,Window> _width;
Getter<int,Window> _height;
void resize(int width,int height)
{
// do actual window resizing logic
_width = width; //using the operator
_height = height; //using the operator
}
};
void someExternalFunction()
{
Window win;
win.resize(640,480); // Ok: public method
int w2 = win._width; //using the operator
//win._height = 480; //KABOOM
}
编辑 修复了硬编码赋值运算符。如果类型本身有赋值运算符,这应该可以很好地工作。默认情况下,结构体具有这些结构,因此对于简单的结构体来说,它应该可以开箱即用。
其他提示
FWIW这是我对你的问题的看法:
- 通常,重点是设置器中强制执行业务逻辑或其他约束。您还可以通过将实例变量与访问器方法解耦来获得计算变量或虚拟变量。
- 从来没听说过。我参与过的项目有一系列 C 宏来消除此类方法
- 是的;我认为这非常整洁。我只是担心这不值得麻烦,它只会让其他开发人员感到困惑(他们需要在头脑中融入另一个概念),并且与手动消除此类方法相比并不会节省太多。
由于伊戈尔Zevaka发布的这一个版本,我会发布一个我写了很久以前。这是略有不同 - 我在那的的时间观察到的最的实际使用的get / set对(实际上做了什么)是预先确定的范围内强制执行变量停留的价值。这是更广泛的一点,如添加的I / O运算符,其中提取器仍然强制限定范围。它也有一个位的测试/演习代号来显示它做什么的总体思路和它是如何做的:
#include <exception>
#include <iostream>
#include <functional>
template <class T, class less=std::less<T> >
class bounded {
const T lower_, upper_;
T val_;
bool check(T const &value) {
return less()(value, lower_) || less()(upper_, value);
}
void assign(T const &value) {
if (check(value))
throw std::domain_error("Out of Range");
val_ = value;
}
public:
bounded(T const &lower, T const &upper)
: lower_(lower), upper_(upper) {}
bounded(bounded const &init)
: lower_(init.lower), upper_(init.upper)
{
assign(init);
}
bounded &operator=(T const &v) { assign(v); return *this; }
operator T() const { return val_; }
friend std::istream &operator>>(std::istream &is, bounded &b) {
T temp;
is >> temp;
if (b.check(temp))
is.setstate(std::ios::failbit);
else
b.val_ = temp;
return is;
}
};
#ifdef TEST
#include <iostream>
#include <sstream>
int main() {
bounded<int> x(0, 512);
try {
x = 21;
std::cout << x << std::endl;
x = 1024;
std::cout << x << std::endl;
}
catch(std::domain_error &e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
std::stringstream input("1 2048");
while (input>>x)
std::cout << x << std::endl;
return 0;
}
#endif
您还可以使用 getter 或 setter 类型方法来获取或设置可计算值,这与 C# 等其他语言中使用属性的方式非常相似
我想不出合理的方法来抽象未知数量的值/属性的获取和设置。
我对 C++ox 标准不够熟悉,无法发表评论。
这可能是在这种情况下矫枉过正,但你应该检查出明智的友谊使用律师/客户端的成语。找到这个成语之前,我避免友谊共
现在的问题是,如果你需要一个 setter
以及。
我不了解你,但我倾向于(大致)有两种类型的课程:
- 逻辑类
- 斑点
Blob 只是业务对象所有属性的松散集合。例如一个 Person
将有一个 surname
, firstname
, ,几个地址,几个职业......所以 Person
可能没有逻辑。
对于 blob,我倾向于使用规范的私有属性 + getter + setter,因为它从客户端抽象了实际的实现。
然而,尽管你的模板(以及 Igor Zeveka 的演变)非常好,但它们没有解决设置问题,也没有解决 二进制兼容性 问题。
我想我可能会求助于宏......
就像是:
// Interface
// Not how DEFINE does not repeat the type ;)
#define DECLARE_VALUE(Object, Type, Name, Seq) **Black Magic Here**
#define DEFINE_VALUE(Object, Name, Seq) ** Black Magic Here**
// Obvious macros
#define DECLARE_VALUER_GETTER(Type, Name, Seq)\
public: boost::call_traits<Type>::const_reference Name() const
#define DEFINE_VALUE_GETTER(Object, Name)\
boost::call_traits<Name##_type>::const_reference Object::Name ()const\
{ return m_##Name; }
#define DECLARE_VALUE_SETTER(Object, Type, Name)\
public: Type& Name();\
public: Object& Name(boost::call_traits<Type>::param_type i);
#define DEFINE_VALUE_SETTER(Object, Name)\
Name##_type& Object::Name() { return m_##Name; }\
Object& Object::Name(boost::call_traits<Name##_type>::param_type i)\
{ m_##Name = i; return *this; }
其用法如下:
// window.h
DECLARE_VALUE(Window, int, width, (GETTER)(SETTER));
// window.cpp
DEFINE_VALUE(Window, width, (GETTER)); // setter needs a bit of logic
Window& Window::width(int i) // Always seems a waste not to return anything!
{
if (i < 0) throw std::logic_error();
m_width = i;
return *this;
} // Window::width
借助一点预处理器的魔力,它会工作得很好!
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/tuple/rem.hpp>
#define DECLARE_VALUE_ITER(r, data, elem)\
DECLARE_VALUE_##elem ( BOOST_PP_TUPLE_REM(3)(data) )
#define DEFINE_VALUE_ITER(r, data, elem)\
DEFINE_VALUE_##elem ( BOOST_PP_TUPLE_REM(2)(data) )
#define DECLARE_VALUE(Object, Type, Name, Seq)\
public: typedef Type Name##_type;\
private: Type m_##Name;\
BOOST_PP_SEQ_FOREACH(DECLARE_VALUE_ITER, (Object, Type, Name), Seq)
#define DEFINE_VALUE(Object, Name, Seq)\
BOOST_PP_SEQ_FOREACH(DEFINE_VALUE_ITER, (Object, Name), Seq)
好吧,不是类型安全,但是:
- 我认为这是一组合理的宏
- 它很容易使用,毕竟用户只需要担心 2 个宏,尽管像模板一样错误可能会变得很严重
- 使用 boost.call_traits 提高效率(const& / 值选择)
那里还有更多功能:吸气剂/吸气剂二人组
不幸的是,它是一组宏......如果您曾经有过,也不会抱怨
- 它对访问器(公共、受保护、私有)造成严重破坏,因此最好不要在整个班级中穿插它
这是典型的例子:
class Window
{
// Best get done with it
DECLARE_VALUE(Window, int, width, (GETTER));
DECLARE_VALUE(Window, int, height, (GETTER));
// don't know which is the current access level, so better define it
public:
};
您正在解决错误的问题。在精心设计的应用程序,getter和setter应罕见,不是自动的。一个有意义的类提供某种抽象的。这不只是成员的集合,它的模型一个概念,就是比它的成员变量只是总和。并且其典型地甚至没有意义,以暴露单独的成员。
一个类应该公开,使在其概念车型感的操作。大多数成员变量在那里保持这种抽象,来存储你所需要的状态。但它通常不应该直接访问。 这就是为什么它是类的在第一位
的私有成员而不是寻找更容易的方式来写car.getFrontLeftWheel()
,问自己,为什么之类的用户会需要摆在首位的左前轮。你通常直接操作该轮驾驶时?这款车是应该照顾所有的车轮空转业务的你,是不是?
这是我认为#define
s仍然是有用的。
在模板版本是复杂,很难理解 - 的定义版本是显而易见
#define Getter(t, n)\
t n;\
t get_##n() { return n; }
class Window
{
Getter(int, height);
}
我相信我有语法稍有不当 - 但你明白了吧
。如果有一个众所周知的一套,比如说模板,提振然后我会使用它们。但我不会写我自己的。