除非我完全弄错,否则getter / setter模式是用于两件事的常见模式:
getVariable
方法(或者,更少见,仅可修改,仅提供setVariable
方法)来创建私有变量,以便可以使用它,但从不修改。 问题#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类模板。
你有什么想法?
答案 0 :(得分:4)
虽然从实施的角度来看解决方案很简洁,但从架构上看,它只有一半。 Getter / Setter模式的要点是让clas控制它的数据并减少耦合(即知道如何数据存储的其他类)。该解决方案实现了前者,但不完全是后者。
事实上,其他班级现在必须知道两件事 - 变量的名称和吸气剂上的方法(即.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
}
编辑修复了硬编码分配运算符。如果类型本身具有赋值运算符,这应该可以很好地工作。默认情况下,结构具有那些对于简单的结构,它应该开箱即用。
对于更复杂的类,您需要实现一个足够公平的赋值运算符。通过RVO和Copy On Write优化,这在运行时应该相当有效。
答案 1 :(得分:1)
FWIW这是我对你的问题的看法:
答案 2 :(得分:1)
自从Igor 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
答案 3 :(得分:0)
您还可以使用getter或setter类型方法来获取或设置可计算值,就像在其他语言中使用属性一样,如C#
我想不出合理的方法来抽象和设置未知数量的值/属性。
我对C ++ ox标准不太熟悉,无法发表评论。
答案 4 :(得分:0)
这是我认为#define
仍然有用的地方。
模板版本复杂且难以理解 - 定义版本很明显
#define Getter(t, n)\
t n;\
t get_##n() { return n; }
class Window
{
Getter(int, height);
}
我确信我的语法略有错误 - 但你明白了。
如果有一组众所周知的模板,比如说,那么我会使用它们。但我不会写自己的。
答案 5 :(得分:0)
在这种情况下,这可能是矫枉过正,但你应该查看律师/客户的习惯用法,以明智的友谊使用。在找到这个成语之前,我完全避免了友谊。
答案 6 :(得分:0)
现在问题是,如果你还需要一个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)
好的,不是安全,而是全部,但是:
那里有更多功能:getter / setter duo
不幸的是,它是一组宏...如果你曾经不会抱怨
这是典型的例子:
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:
};
答案 7 :(得分:0)
你正在解决错误的问题。在精心设计的应用程序中,getter和setter应该罕见,而不是自动化的。一个有意义的类提供某种抽象。它不仅仅是一个成员集合,它模拟的概念不仅仅是其成员变量的总和。揭露个别成员通常甚至没有意义。
一个类应该公开对它建模的概念有意义的操作。大多数成员变量用于维护此抽象,以存储您需要的状态。但通常不应直接访问它。 这就是为什么它首先是该类的私人成员。
不要找到更简单的方法来编写car.getFrontLeftWheel()
,而要问问自己为什么班级的用户首先需要左前轮。你经常在驾驶时直接操纵那个轮子吗?汽车应该为你照顾所有的车轮纺纱业务,不是吗?